1
0

storage-2: Add directory listing service with DAV file upload

This commit is contained in:
clerie 2022-08-14 21:04:21 +02:00
parent 3f3fb0fc19
commit d1c7267119
3 changed files with 360 additions and 0 deletions

View File

@ -7,6 +7,7 @@
../../configuration/common ../../configuration/common
../../configuration/proxmox-vm ../../configuration/proxmox-vm
./mixcloud.nix ./mixcloud.nix
./storage.nix
]; ];
boot.loader.grub.enable = true; boot.loader.grub.enable = true;

View File

@ -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.clerie.de</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>

View File

@ -0,0 +1,38 @@
{ ... }:
{
services.nginx.virtualHosts = {
"storage.clerie.de" = {
enableACME = true;
forceSSL = true;
basicAuthFile = "/var/src/secrets/nginx/storage.htpasswd";
locations."= /" = {
alias = "${./filemanager}/";
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";
};
}