aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md13
-rw-r--r--TODO13
-rw-r--r--assets/admin.css36
-rw-r--r--assets/cargohold.css35
-rw-r--r--assets/cargohold.js22
-rw-r--r--backend/Admin.py19
-rw-r--r--backend/config.py6
-rw-r--r--backend/utils.py16
-rw-r--r--interface/admin_aliases.tmpl40
-rw-r--r--interface/admin_delete.tmpl21
-rw-r--r--interface/admin_dirs.tmpl6
-rw-r--r--interface/admin_files.tmpl21
-rw-r--r--interface/admin_rename.tmpl40
-rw-r--r--interface/admin_upload.tmpl36
-rw-r--r--interface/listing.htm6
-rw-r--r--interface/listing_default.tmpl58
17 files changed, 357 insertions, 32 deletions
diff --git a/.gitignore b/.gitignore
index bec372b..ad10a24 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*.swp
__pycache__
+backend/cargohold.db3
diff --git a/README.md b/README.md
index eb59c09..f30d904 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ Required dependencies
* uwsgi-plugin-python3
* sqlite3
* python3
+* python3-jinja2
+* python3-passlib if using `LocalBasicAuth`
Basic infrastructure
@@ -59,4 +61,13 @@ Other httpds may provide a similar mechanism, which will need to be called out t
## Authentication
-TBD
+This projects provides multiple methods to identify and authenticate users, including delegating authentication to external software.
+Implementing custom authentication methods is possible and patches contributing such are welcome.
+
+The following authentication methods are currently available:
+
+* *NoneAuth*: The default. Does not support any logins. Use this if you want to manage cargohold exclusively via the command line or direct database interfaces
+* *ExternalAuth*: Delegate authentication to an external entity. Expects the authenticated user (if any) in the REMOTE_USER environment variable
+* *LocalBasicAuth*: Internal implementation of HTTP Basic Auth using htpasswd-style user databases
+* *RemoteCookieAuth*: Custom authentication scheme validating a domain-wide cookie against an external service
+*
diff --git a/TODO b/TODO
index 4964457..695e0a9 100644
--- a/TODO
+++ b/TODO
@@ -2,8 +2,17 @@ Size limiting
HTML-only frontend
Automatic resizing for gallery mode
Alias titles
-'Download all' button
-
+Password protection per alias
+'Scramble' file links (do not make the parent directory available via the individual links)
Admin
Size limit administration
+ Add aliases
+ Add specific aliases?
+
+JS Admin
+ Rename
+ Create Alias
+ Delete
+
+Storage size calculation
diff --git a/assets/admin.css b/assets/admin.css
index 4fdc5fb..bfa854c 100644
--- a/assets/admin.css
+++ b/assets/admin.css
@@ -26,20 +26,44 @@ div.entry {
vertical-align: center;
}
-div.entry a.button {
+.info, .permission {
padding: 0.5em;
background-color: #c8e;
- text-decoration: none;
- margin-left: 0.5em;
- display: block;
border-radius: 0.5em;
+ margin-left: 0.5em;
+ text-decoration: none;
+ color: #453;
}
-a.delete {
+.button.delete {
background-color: #f79 !important;
}
-a.directory {
+.button.active {
+ background-color: #569;
+}
+
+.permission {
+ background-color: #366;
+ color: #acb;
+}
+
+.permission.disabled {
+ text-decoration: line-through;
+ background-color: #636;
+}
+
+a.entry {
text-decoration: none;
color: #aade88;
}
+
+span.dirname {
+ font-weight: bold;
+ font-style: italic;
+}
+
+#topmenu, #action {
+ display: inline-flex;
+ margin: 1em;
+}
diff --git a/assets/cargohold.css b/assets/cargohold.css
index 747b916..eddd1c7 100644
--- a/assets/cargohold.css
+++ b/assets/cargohold.css
@@ -56,7 +56,7 @@ html {
}
#dirlisting {
- padding: 2em;
+ padding: 0 2em 2em 2em;
}
#storage {
@@ -98,9 +98,14 @@ html {
background-color: #728;
padding: 0.5em;
text-align: left;
+}
+
+#header h1 {
font-size: 120%;
font-weight: bold;
font-family: sans;
+ display: inline-block;
+ margin: 0;
}
#container {
@@ -108,6 +113,24 @@ html {
width: 85%;
}
+a.button {
+ padding: 0.5em;
+ background-color: #c8e;
+ text-decoration: none;
+ margin-left: 0.5em;
+ border-radius: 0.5em;
+}
+
+.buttonrow {
+ display: block;
+ text-align: right;
+ padding: 0.5em;
+}
+
+.buttonrow > a {
+ display: inline-block;
+}
+
.tab-wrap > input {
display: none;
}
@@ -167,3 +190,13 @@ html {
border-radius: 1em;
background-color: #8885;
}
+
+.footer, .footer a {
+ text-align: right;
+ text-decoration: none;
+ color: #777;
+}
+
+.footer {
+ padding: 1em;
+}
diff --git a/assets/cargohold.js b/assets/cargohold.js
index 18dbeae..ea882fa 100644
--- a/assets/cargohold.js
+++ b/assets/cargohold.js
@@ -7,7 +7,6 @@
var running = false;
var queue = [];
-
function queue_work(){
let i = 0;
for(; i < queue.length; i++){
@@ -118,6 +117,7 @@ function interface_update(access){
function listing_add(file, style = "default"){
let link = node("a", "listing-entry", file.name);
link.href = "file/" + encodeURIComponent(file.name);
+ link.setAttribute("data-filename", file.name)
if(style == "gallery"){
let image = node("img", "preview");
@@ -139,14 +139,34 @@ function listing_update(){
for(let i = 0; i < data.files.length; i++){
listing_add(data.files[i], data.display);
}
+
+ element("download-all").innerText = "Download all (" + data.files.length + " Files, " + (data.total / 1024 /1024) + "MB)";
};
req.open("GET", "listing");
req.send();
}
+function download_all(ev){
+ let links = document.getElementsByClassName("listing-entry");
+ var current = 0;
+ var interval = setInterval(function(){
+ if(current < links.length){
+ link = links[current];
+ link.download = link.getAttribute("data-filename");
+ link.click();
+ link.removeAttribute("download");
+ current++;
+ }
+ else{
+ clearInterval(interval);
+ }
+ }, 1000);
+}
+
function init(){
element("file-submit").style.display = "none";
element("files").onchange = upload_start;
+ element("download-all").onclick = download_all;
listing_update();
}
diff --git a/backend/Admin.py b/backend/Admin.py
index a844aff..2070293 100644
--- a/backend/Admin.py
+++ b/backend/Admin.py
@@ -8,7 +8,10 @@ template_factory = jinja2.Environment(loader=jinja2.FileSystemLoader('../interfa
admin_dirs = template_factory.get_template("admin_dirs.tmpl")
admin_files = template_factory.get_template("admin_files.tmpl")
+admin_upload = template_factory.get_template("admin_upload.tmpl")
admin_delete = template_factory.get_template("admin_delete.tmpl")
+admin_rename = template_factory.get_template("admin_rename.tmpl")
+admin_aliases = template_factory.get_template("admin_aliases.tmpl")
def route(path, env, post):
auth = config.Auth.get(env)
@@ -39,11 +42,19 @@ def route(path, env, post):
files = utils.dirlisting(directory, True, False)
return [admin_files.render({"user": auth["user"], "listing": files, "directory": utils.sanitize_filename(path[1])}), [("Content-Type", "text/html")], "200 OK"]
+ # Upload
+ if len(path) == 3 and path[2] == "upload":
+ return [admin_upload.render({"user": auth["user"], "directory": utils.sanitize_filename(path[1])}), [("Content-Type", "text/html")], "200 OK"]
+
# Alias management / Limits config
- # TODO
+ if len(path) == 3 and path[2] == "aliases":
+ aliases = utils.aliases(auth["user"], utils.sanitize_filename(path[1]))
+ return [admin_aliases.render({"user": auth["user"], "directory": utils.sanitize_filename(path[1]), "aliases": aliases, "baseurl": config.baseurl}), [("Content-Type", "text/html")], "200 OK"]
# Renaming
- # TODO
+ if len(path) == 3 and path[2] == "rename":
+ # TODO
+ return [admin_rename.render({"user": auth["user"], "directory": utils.sanitize_filename(path[1])}), [("Content-Type", "text/html")], "200 OK"]
# Deletion
if len(path) == 3 and path[2] == "delete":
@@ -52,9 +63,7 @@ def route(path, env, post):
return utils.redirect("/admin")
files = utils.dirlisting(directory, True, False)
if len(files) == 0 or env.get("QUERY_STRING", "") == "confirm":
- # TODO remove contents
- # TODO remove aliases
- os.rmdir(directory)
+ utils.cleanup(auth["user"], utils.sanitize_filename(path[1]))
return utils.redirect("/admin")
return [admin_delete.render({"user": auth["user"], "listing": files, "directory": utils.sanitize_filename(path[1])}), [("Content-Type", "text/html")], "200 OK"]
diff --git a/backend/config.py b/backend/config.py
index 2de63bc..a4a1209 100644
--- a/backend/config.py
+++ b/backend/config.py
@@ -8,5 +8,11 @@ global_limit = 0
user_subdirs = True
# Path to the backing database
database = "cargohold.db3"
+
+# Settings for the web admin panel
+
+# Base URL for rendering alias links
+baseurl = "https://files.stumpf.es/"
+
# Select the authentication provider for the web admin interface
import NoneAuth as Auth
diff --git a/backend/utils.py b/backend/utils.py
index bab7ebe..1e23e63 100644
--- a/backend/utils.py
+++ b/backend/utils.py
@@ -57,3 +57,19 @@ def dirlisting(path, files, dirs):
listing.append({"name": entry, "size": size})
return listing
+
+def cleanup(user, subdir):
+ directory = userdir(user) + subdir + "/"
+ print("Cleaning up %s" % (directory, ))
+ # TODO
+ # Remove contents
+ # Remove aliases
+ os.rmdir(directory)
+ return
+
+def aliases(user, subdir):
+ aliases = []
+ data = db.cursor().execute("SELECT alias, access, storage, display FROM aliases WHERE user = :user AND real = :dir", {"user": user, "dir": subdir}).fetchall()
+ for alias in data:
+ aliases.append({"alias": alias[0], "access": alias[1], "storage": alias[2], "display": alias[3]})
+ return aliases
diff --git a/interface/admin_aliases.tmpl b/interface/admin_aliases.tmpl
new file mode 100644
index 0000000..97315f7
--- /dev/null
+++ b/interface/admin_aliases.tmpl
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="robots" content="noindex,nofollow" />
+ <title>cargohold</title>
+ <link rel="stylesheet" href="/assets/cargohold.css" />
+ <link rel="stylesheet" href="/assets/admin.css" />
+ <link rel="icon" href="/assets/cargohold.ico" />
+ </head>
+ <body>
+ <div id="header">
+ <h1>cargohold - <span class="dirname">{{ directory }}</span></h1>
+ <a class="button" href="/admin">Back to overview</a>
+ </div>
+
+ <div id="container">
+ <div id="topmenu">
+ <a class="button" href="/admin/{{ directory }}">View</a>
+ <a class="button" href="/admin/{{ directory }}/upload">Upload</a>
+ <a class="button" href="/admin/{{ directory }}/rename">Rename</a>
+ <a class="button active" href="/admin/{{ directory }}/aliases">Links</a>
+ <a class="button delete" href="/admin/{{ directory }}/delete">Delete</a>
+ </div>
+
+ <div id="listing">
+ {% for alias in aliases %}
+ <div class="entry">
+ <a class="entry" href="{{ baseurl }}{{ alias.alias }}">{{ baseurl }}{{ alias.alias }}</a>
+ <a class="permission {% if 'r' not in alias.access %}disabled{% endif %}" href="#">Download</a>
+ <a class="permission {% if 'c' not in alias.access %}disabled{% endif %}" href="#">Upload</a>
+ <a class="permission {% if 'd' not in alias.access %}disabled{% endif %}" href="#">Delete</a>
+ <a class="permission" href="#">Previews</a>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/interface/admin_delete.tmpl b/interface/admin_delete.tmpl
index bf6d8a2..102d908 100644
--- a/interface/admin_delete.tmpl
+++ b/interface/admin_delete.tmpl
@@ -11,24 +11,35 @@
</head>
<body>
<div id="header">
- cargohold - Administration
+ <h1>cargohold - <span class="dirname">{{ directory }}</span></h1>
+ <a class="button" href="/admin">Back to overview</a>
</div>
<div id="container">
+ <div id="topmenu">
+ <a class="button" href="/admin/{{ directory }}">View</a>
+ <a class="button" href="/admin/{{ directory }}/upload">Upload</a>
+ <a class="button" href="/admin/{{ directory }}/rename">Rename</a>
+ <a class="button" href="/admin/{{ directory }}/aliases">Links</a>
+ <a class="button active" href="/admin/{{ directory }}/delete">Delete</a>
+ </div>
+
<div id="message">
- This will delete the directory <span class="highlight">{{ directory }}</span>.
+ This will delete the directory <span class="highlight dirname">{{ directory }}</span>.
<br />
This directory still contains {{ listing | length }} files.
</div>
+
<div id="dirlisting">
{% for file in listing %}
<div class="entry">
- <a class="file" href="#">{{ file.name }}</a>
+ <a class="entry" href="/admin/{{ directory }}/file/{{ file.name }}">{{ file.name }}</a>
</div>
{% endfor %}
</div>
+
<div id="action">
- <a class="button" href="/admin">Cancel</a>
- <a class="button delete" href="?confirm">Confirm &amp; delete</a>
+ <a class="button" href="/admin/{{ directory }}">Cancel</a>
+ <a class="button delete" href="?confirm">Confirm &amp; delete all</a>
</div>
</div>
</body>
diff --git a/interface/admin_dirs.tmpl b/interface/admin_dirs.tmpl
index cebaa62..d7c5317 100644
--- a/interface/admin_dirs.tmpl
+++ b/interface/admin_dirs.tmpl
@@ -11,7 +11,7 @@
</head>
<body>
<div id="header">
- cargohold - Administration
+ <h1>cargohold - All directories</h1>
</div>
<div id="container">
{% if error %}
@@ -28,9 +28,9 @@
<div id="dirlisting">
{% for dir in listing %}
<div class="entry">
- <a class="directory" href="/admin/{{dir.name}}">{{dir.name}}</a>
+ <a class="entry" href="/admin/{{dir.name}}">{{dir.name}}</a>
<a class="button" href="/admin/{{dir.name}}/rename">Rename</a>
- <a class="button" href="/admin/{{dir.name}}/aliases">Aliases</a>
+ <a class="button" href="/admin/{{dir.name}}/aliases">{{dir.nlinks}} Links</a>
<a class="delete button" href="/admin/{{dir.name}}/delete">Delete</a>
</div>
{% endfor %}
diff --git a/interface/admin_files.tmpl b/interface/admin_files.tmpl
index 76b63d5..76e97aa 100644
--- a/interface/admin_files.tmpl
+++ b/interface/admin_files.tmpl
@@ -11,18 +11,25 @@
</head>
<body>
<div id="header">
- cargohold - Administration
+ <h1>cargohold - <span class="dirname">{{ directory }}</span></h1>
+ <a class="button" href="/admin">Back to overview</a>
</div>
+
<div id="container">
- {% if message %}
- <div id="message">
- {{ message }}
- </div>
- {% endif %}
+ <div id="topmenu">
+ <a class="button active" href="/admin/{{ directory }}">View</a>
+ <a class="button" href="/admin/{{ directory }}/upload">Upload</a>
+ <a class="button" href="/admin/{{ directory }}/rename">Rename</a>
+ <a class="button" href="/admin/{{ directory }}/aliases">Links</a>
+ <a class="button delete" href="/admin/{{ directory }}/delete">Delete</a>
+ </div>
+
<div id="dirlisting">
{% for file in listing %}
<div class="entry">
- <a class="file" href="#">{{ file.name }}</a>
+ <a class="entry" href="/admin/{{ directory }}/file/{{ file.name | urlencode }}">{{ file.name }}</a>
+ <div class="info">{{ (file.size / 1024 / 1024) | round(2, "ceil") }} MB</div>
+ <a class="button delete" href="/admin/{{ directory }}/delete/{{ file.name | urlencode }}">Delete</a>
</div>
{% endfor %}
</div>
diff --git a/interface/admin_rename.tmpl b/interface/admin_rename.tmpl
new file mode 100644
index 0000000..a760cb5
--- /dev/null
+++ b/interface/admin_rename.tmpl
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="robots" content="noindex,nofollow" />
+ <title>cargohold</title>
+ <link rel="stylesheet" href="/assets/cargohold.css" />
+ <link rel="stylesheet" href="/assets/admin.css" />
+ <link rel="icon" href="/assets/cargohold.ico" />
+ </head>
+ <body>
+ <div id="header">
+ <h1>cargohold - <span class="dirname">{{ directory }}</span></h1>
+ <a class="button" href="/admin">Back to overview</a>
+ </div>
+
+ <div id="container">
+ <div id="topmenu">
+ <a class="button" href="/admin/{{ directory }}">View</a>
+ <a class="button" href="/admin/{{ directory }}/upload">Upload</a>
+ <a class="button active" href="/admin/{{ directory }}/rename">Rename</a>
+ <a class="button" href="/admin/{{ directory }}/aliases">Links</a>
+ <a class="button delete" href="/admin/{{ directory }}/delete">Delete</a>
+ </div>
+
+ <div>
+ This will rename the directory <span class="highlight dirname">{{ directory }}</span>.
+ <br />
+ All aliases will be kept intact.
+ </div>
+
+ <input type="text" placeholder="New name" />
+
+ <div id="action">
+ <a class="button" href="?confirm">Rename</a>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/interface/admin_upload.tmpl b/interface/admin_upload.tmpl
new file mode 100644
index 0000000..be5b298
--- /dev/null
+++ b/interface/admin_upload.tmpl
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="robots" content="noindex,nofollow" />
+ <title>cargohold</title>
+ <link rel="stylesheet" href="/assets/cargohold.css" />
+ <link rel="stylesheet" href="/assets/admin.css" />
+ <link rel="icon" href="/assets/cargohold.ico" />
+ </head>
+ <body>
+ <div id="header">
+ <h1>cargohold - <span class="dirname">{{ directory }}</span></h1>
+ <a class="button" href="/admin">Back to overview</a>
+ </div>
+
+ <div id="container">
+ <div id="topmenu">
+ <a class="button" href="/admin/{{ directory }}">View</a>
+ <a class="button active" href="/admin/{{ directory }}/upload">Upload</a>
+ <a class="button" href="/admin/{{ directory }}/rename">Rename</a>
+ <a class="button" href="/admin/{{ directory }}/aliases">Links</a>
+ <a class="button delete" href="/admin/{{ directory }}/delete">Delete</a>
+ </div>
+
+ <div>
+ TBD
+ </div>
+
+ <div id="action">
+ <a class="button" href="#">Upload</a>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/interface/listing.htm b/interface/listing.htm
index 5866825..d7d08cd 100644
--- a/interface/listing.htm
+++ b/interface/listing.htm
@@ -11,7 +11,7 @@
</head>
<body>
<div id="header">
- cargohold
+ <h1>cargohold</h1>
</div>
<div id="container">
<div class="tab-wrap">
@@ -30,6 +30,9 @@
current link.
</div>
</noscript>
+ <div class="buttonrow">
+ <a class="button" href="#" id="download-all">Download all</a>
+ </div>
<div id="dirlisting">
</div>
</div>
@@ -55,5 +58,6 @@
</div>
</div>
</div>
+ <div class="footer"><a href="https://git.services.cbcdn.com/cargohold/">cargohold</a> is a free and open source project</div>
</body>
</html>
diff --git a/interface/listing_default.tmpl b/interface/listing_default.tmpl
new file mode 100644
index 0000000..4a6d8bc
--- /dev/null
+++ b/interface/listing_default.tmpl
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="robots" content="noindex,nofollow" />
+ <title>cargohold</title>
+ <link rel="stylesheet" href="/assets/cargohold.css" />
+ <link rel="icon" href="/assets/cargohold.ico" />
+ <script src="/assets/cargohold.js"></script>
+ </head>
+ <body>
+ <div id="header">
+ <h1>cargohold</h1>
+ </div>
+ <div id="container">
+ <div class="tab-wrap">
+ <input type="radio" id="tab-view" name="tabs" class="tab" checked>
+ <label for="tab-view" id="tab-view-label">View</label>
+
+ <input type="radio" id="tab-upload" name="tabs" class="tab">
+ <label for="tab-upload" id="tab-upload-label">Upload</label>
+
+ <div class="tab-content">
+ <div class="buttonrow">
+ <a class="button" href="#" id="download-all">Download all</a>
+ </div>
+ <div id="dirlisting">
+ {% for file in listing %}
+ <a class="listing-entry" href="file/{{ file.name | urlencode }}" data-filename="{{ file.name }}">{{ file.name }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ <div class="tab-content">
+ <div id="upload">
+ <form action="upload" method="post" enctype="multipart/form-data">
+ <label for="files" id="selector-label">
+ △ Select files to upload
+ <input type="file" id="files" name="files" multiple>
+ </label>
+ <input type="submit" value="Start upload" name="submit" id="file-submit">
+
+ </form>
+ </div>
+ <div id="queue">
+ </div>
+ <!--
+ <div id="storage">
+ Storage space left: 2GB / 2GB <br/>
+ <progress value="70" max="100">70 %</progress>
+ </div>
+ -->
+ </div>
+ </div>
+ </div>
+ <div class="footer"><a href="https://git.services.cbcdn.com/cargohold/">cargohold</a> is a free and open source project</div>
+ </body>
+</html>