commit 5d0a9bd66051e17c52d482145d41eea4c10b21bb
parent 602fa6d112f7c9e7ce281c82e3c6ffd477d54c5c
Author: Leah (ctucx) <leah@ctu.cx>
Date: Thu, 18 Aug 2022 11:07:12 +0200
parent 602fa6d112f7c9e7ce281c82e3c6ffd477d54c5c
Author: Leah (ctucx) <leah@ctu.cx>
Date: Thu, 18 Aug 2022 11:07:12 +0200
machines/lollo/websites: add storage.home.ctu.cx
4 files changed, 362 insertions(+), 0 deletions(-)
A
|
321
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/machines/lollo/router/dnsmasq.nix b/machines/lollo/router/dnsmasq.nix @@ -44,6 +44,7 @@ in { host-record=legacy.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1 host-record=dnsmasq.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1 host-record=music.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1 + host-record=storage.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1 host-record=influx.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1 host-record=wiki.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1 host-record=fedi.home.ctu.cx, 195.39.246.41, 2a0f:4ac0:acab::1
diff --git a/machines/lollo/websites/default.nix b/machines/lollo/websites/default.nix @@ -5,6 +5,7 @@ imports = [ ./wiki.home.ctu.cx.nix ./music.home.ctu.cx.nix + ./storage.home.ctu.cx ]; }
diff --git a/machines/lollo/websites/storage.home.ctu.cx/default.nix b/machines/lollo/websites/storage.home.ctu.cx/default.nix @@ -0,0 +1,39 @@ + +{ ... }: + +{ + services.nginx.virtualHosts = { + "storage.home.ctu.cx" = { + enableACME = true; + forceSSL = true; + + basicAuthFile = "/var/src/secrets/nginx/storage.htpasswd"; + + locations."= /" = { + alias = "${./web-root}/"; + extraConfig = '' + try_files index.html =404; + ''; + }; + + locations."/_/" = { + alias = "/data/"; + extraConfig = '' + autoindex on; + autoindex_format json; + + client_body_temp_path /data; + dav_methods PUT DELETE MKCOL COPY MOVE; + create_full_put_path on; + dav_access group:rw all:r; + client_max_body_size 1G; + ''; + }; + }; + }; + + systemd.services.nginx.serviceConfig = { + ReadWritePaths = "/data"; + }; +} +
diff --git a/machines/lollo/websites/storage.home.ctu.cx/web-root/index.html b/machines/lollo/websites/storage.home.ctu.cx/web-root/index.html @@ -0,0 +1,321 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <title>storage.home.ctu.cx</title> + + <style> + * { + margin: 0; + padding: 0; + } + + body { + font-family: Roboto; + color: #222222; + } + + header { + } + + header > .title { + padding: 3em; + min-height: 3em; + } + + header > .title > h1 { + color: #222222; + } + + header > .menu { + padding: 1em 3em; + } + + header > .menu > button, + header > .menu > input { + height: 3em; + min-width: 3em; + } + + main { + } + + .listing { + display: flex; + width: 100%; + flex-direction: column; + } + + .listing-item { + padding: 1em 3em; + display: flex; + flex-direction: row; + flex-wrap: wrap; + min-height: 3em; + + border-top-style: solid; + border-width: 1px; + border-color: #F0F0F0; + } + + .listing-item:hover { + background-color: rgb(245, 245, 245); + } + + .listing-item > .detail { + flex: auto; + } + + .listing-item > .detail > a { + display: block; + height: 100%; + width: 100%; + } + + .listing-item button { + height: 3em; + min-width: 3em; + } + + button, input[type="file"] { + border-style: none; + background-color: transparent; + min-width: 3em; + height: 3em; + border-radius: 0.2em; + } + + button:hover { + background-color: #DDDDDD; + } + + .loading { + margin: auto; + } + @keyframes spinner { + 0% { + transform: translate3d(-50%, -50%, 0) rotate(0deg); + } + 100% { + transform: translate3d(-50%, -50%, 0) rotate(360deg); + } + } + .loading::before { + display: block; + animation: 1.5s linear infinite spinner; + animation-play-state: inherit; + border: solid 5px #cfd0d1; + border-bottom-color: #1c87c9; + border-radius: 50%; + content: ""; + height: 40px; + width: 40px; + transform: translate3d(-50%, -50%, 0); + will-change: transform; + } + </style> + </head> + <body> + <header> + <div class="title"> + <h1 id="path"></h1> + </div> + <div class="menu"> + <button onclick="createDirectoryButton();">📁 +</button> + <input id="files" type="file" multiple> <button onclick="uploadFilesButton();">⭫</button> + </div> + </header> + <main> + <div id="listing"></div> + </main> + <script> + let path_field = document.getElementById("path"); + let listing_field = document.getElementById("listing"); + + let base_path = "/_"; + + if (window.location.hash === "") { + window.location.hash = "#/"; + } + + let current_path = window.location.hash.substring(1); + + function deleteFile(path) { + fetch(base_path + path, { + method: 'DELETE', + }) + .then((response) => { + console.log(response); + if (!response.ok) { + throw new Error("Removing file failed"); + } + }) + .then(() => { + listFiles(); + }); + } + + function deleteFileButton(path) { + if (confirm("Are you sure you wanna delete " + path + "?")) { + deleteFile(path); + } + } + + function createDirectory(path) { + fetch(base_path + path, { + method: 'MKCOL', + }) + .then((response) => { + console.log(response); + if (!response.ok) { + throw new Error("Creating directory failed"); + } + }) + .then(() => { + listFiles(); + }); + } + + function createDirectoryButton() { + let dir_name = prompt("Directory name"); + if (dir_name !== null) { + createDirectory(current_path + dir_name + "/"); + } + } + + function uploadFile(path, file) { + + fetch(base_path + path, { + method: 'PUT', + body: file, + }) + .then((response) => { + console.log(response); + if (!response.ok) { + throw new Error("Upload failed"); + } + }) + .then(() => { + listFiles(); + }); + } + + function uploadFilesButton() { + let files = document.getElementById("files").files; + for (let i = 0; i < files.length; i++) { + uploadFile(current_path + files[i].name, files[i]); + } + document.getElementById("files").value = ""; + } + + function moveFile(old_path, new_path) { + fetch(base_path + old_path, { + method: 'MOVE', + headers: { + 'Destination': base_path + new_path, + }, + }) + .then((response) => { + console.log(response); + if (!response.ok) { + throw new Error("Rename file failed"); + } + }) + .then(() => { + listFiles(); + }); + } + + function moveFileButton(path) { + let new_path = prompt("New file path", path); + moveFile(path, new_path); + } + + function renameFileButton(path) { + let new_name = prompt("New file name"); + moveFile(path, current_path + new_name); + } + + function copyFile(path, new_path) { + fetch(base_path + path, { + method: 'COPY', + headers: { + 'Destination': base_path + new_path, + }, + }) + .then((response) => { + console.log(response); + if (!response.ok) { + throw new Error("Copy file failed"); + } + }) + .then(() => { + listFiles(); + }); + } + + function duplicateFileButton(path) { + let new_path = prompt("New file path", path); + copyFile(path, new_path); + } + + function listFiles() { + listing_field.innerHTML = '<div class="loading"></div>'; + + path_field.innerHTML = '<a href="#/">⌂</a>'; + + let path_blocks = current_path.split('/').slice(1); + for (let i = 0; i < path_blocks.length; i++) { + path_field.innerHTML += ' / <a href="#/' + path_blocks.slice(0, i+1).join('/') + '/">' + path_blocks[i] + '</a>'; + } + + fetch(base_path + current_path) + .then((response) => { + console.log(response); + if (!response.ok) { + throw new Error("Can't fetch directory"); + } + return response.json(); + }) + .then((listing) => { + console.log(listing); + let out = '<div class="listing">'; + for (let i = 0; i < listing.length; i++) { + let listing_path = current_path + encodeURIComponent(listing[i]["name"]); + let listing_link = "#"; + let listing_icon = ""; + if (listing[i]["type"] === "directory") { + listing_path += '/'; + listing_link = '#' + listing_path; + listing_icon = "📁"; + } + else { + listing_link = base_path + listing_path; + } + out += '<div class="listing-item">' + + '<div class="detail">' + + '<a href="' + listing_link + '">' + listing_icon + " " + listing[i]["name"] + '</a>' + + '</div>' + + '<div class="options">' + + '<button onclick="renameFileButton(\'' + listing_path + '\');" alt="rename">✎</button>' + + '<button onclick="duplicateFileButton(\'' + listing_path + '\');">●●</button>' + + '<button onclick="moveFileButton(\'' + listing_path + '\');">⇨</button>' + + '<button onclick="deleteFileButton(\'' + listing_path + '\');">❌</button>' + + '</div>' + + '</div>'; + } + out += '</div>'; + listing_field.innerHTML = out; + }); + } + + listFiles(); + + window.onhashchange = () => { + current_path = window.location.hash.substring(1); + listFiles(); + } + </script> + </body> +</html>