From 384874d4f5999b41c9397451995f86b067d5ade9 Mon Sep 17 00:00:00 2001 From: lelo Date: Sat, 3 May 2025 14:01:24 +0200 Subject: [PATCH] implement folder config for admin --- .gitignore | 2 +- analytics.py | 8 +- app.py | 11 +- auth.py | 23 +- folder_secret_config_editor.py | 63 ++++ templates/app.html | 5 + templates/base.html | 71 +++++ templates/connections.html | 335 ++++++++------------- templates/dashboard.html | 56 +--- templates/folder_secret_config_editor.html | 187 ++++++++++++ templates/mylinks.html | 220 +++++++------- templates/songs_dashboard.html | 51 +--- 12 files changed, 614 insertions(+), 418 deletions(-) create mode 100644 folder_secret_config_editor.py create mode 100644 templates/base.html create mode 100644 templates/folder_secret_config_editor.html diff --git a/.gitignore b/.gitignore index 26464f9..e12ff74 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ /search.db /access_log.db /access_log.db.bak -/folder_permission_config.json +/folder_secret_config.json /folder_mount_config.json /.env /app_config.json diff --git a/analytics.py b/analytics.py index d10d8dc..4d562aa 100644 --- a/analytics.py +++ b/analytics.py @@ -6,6 +6,7 @@ from auth import require_secret from collections import defaultdict import json import os +import auth file_access_temp = [] @@ -189,11 +190,11 @@ def songs_dashboard(): performance_data = [(count, titel) for titel, count in performance_counts.items()] performance_data.sort(reverse=True, key=lambda x: x[0]) - return render_template('songs_dashboard.html', timeframe=timeframe_param, performance_data=performance_data, site=site, category=category) + return render_template('songs_dashboard.html', timeframe=timeframe_param, performance_data=performance_data, site=site, category=category, admin_enabled=auth.is_admin()) @require_secret def connections(): - return render_template('connections.html') + return render_template('connections.html', admin_enabled=auth.is_admin()) @require_secret def dashboard(): @@ -476,7 +477,8 @@ def dashboard(): unique_files=unique_files, unique_user=unique_user, cached_percentage=cached_percentage, - timeframe_data=timeframe_data + timeframe_data=timeframe_data, + admin_enabled=auth.is_admin() ) def export_to_excel(): diff --git a/app.py b/app.py index a9a0e8a..2de3d22 100755 --- a/app.py +++ b/app.py @@ -22,11 +22,12 @@ import base64 import search import auth import analytics as a +import folder_secret_config_editor as fsce with open("app_config.json", 'r') as file: app_config = json.load(file) -with open('folder_permission_config.json') as file: +with open('folder_secret_config.json') as file: folder_config = json.load(file) cache_audio = diskcache.Cache('./filecache_audio', size_limit= app_config['filecache_size_limit_audio'] * 1024**3) @@ -53,6 +54,9 @@ app.add_url_rule('/searchcommand', view_func=search.searchcommand, methods=['POS app.add_url_rule('/songs_dashboard', view_func=a.songs_dashboard) +app.add_url_rule('/admin/folder_secret_config_editor', view_func=auth.require_admin(fsce.folder_secret_config_editor), methods=['GET', 'POST']) +app.add_url_rule('/admin/folder_secret_config_editor/data', view_func=auth.require_admin(fsce.folder_secret_config_data)) +app.add_url_rule('/admin/folder_secret_config_editor/action', view_func=auth.require_admin(fsce.folder_secret_config_action), methods=['POST']) # Grab the HOST_RULE environment variable host_rule = os.getenv("HOST_RULE", "") @@ -548,14 +552,11 @@ def handle_request_initial_data(): def index(path): title_short = app_config.get('TITLE_SHORT', 'Default Title') title_long = app_config.get('TITLE_LONG' , 'Default Title') - admin_enabled = False - if 'admin' in session: - admin_enabled = session['admin'] return render_template("app.html", title_short=title_short, title_long=title_long, - admin_enabled=admin_enabled, + admin_enabled=auth.is_admin() ) if __name__ == '__main__': diff --git a/auth.py b/auth.py index d0ed94c..f207c5b 100644 --- a/auth.py +++ b/auth.py @@ -17,9 +17,24 @@ import hashlib with open("app_config.json", 'r') as file: app_config = json.load(file) -with open('folder_permission_config.json') as file: +with open('folder_secret_config.json') as file: folder_config = json.load(file) +def is_admin(): + """ + Check if the user is an admin based on the session. + """ + return session.get('admin', False) + +def require_admin(f): + @wraps(f) + @require_secret + def decorated_function(*args, **kwargs): + if is_admin(): + return f(*args, **kwargs) + else: + return "You don't have admin permission", 403 + return decorated_function def require_secret(f): @wraps(f) @@ -104,8 +119,10 @@ def require_secret(f): if args_admin and config_admin: if args_admin == config_admin: + print(f"Admin access granted with key: {args_admin}") session['admin'] = True else: + print(f"Admin access denied with key: {args_admin}") session['admin'] = False # 6) If we have folders, proceed; otherwise show index @@ -129,6 +146,7 @@ def require_secret(f): header_text_color=header_text_color, main_text_color=main_text_color, background_color=background_color, + admin_enabled=is_admin() ) return decorated_function @@ -197,7 +215,8 @@ def mylinks(): token_qr_codes=token_qr_codes, token_folders=token_folders, token_url=token_url, - token_valid_to=token_valid_to + token_valid_to=token_valid_to, + admin_enabled=is_admin() ) diff --git a/folder_secret_config_editor.py b/folder_secret_config_editor.py new file mode 100644 index 0000000..de07349 --- /dev/null +++ b/folder_secret_config_editor.py @@ -0,0 +1,63 @@ +from flask import Flask, request, jsonify, render_template +import json +import os +from datetime import datetime +import secrets +import string +import auth + + +DATA_FILE = 'folder_secret_config.json' + +# Secret alphabet +ALPHABET = string.ascii_letters + string.digits + +@auth.require_admin +def load_data(): + with open(DATA_FILE) as f: + try: + data = json.load(f) + print(f"Loaded {len(data)} records from {DATA_FILE}.") + return data + except: + print(f"Error loading {DATA_FILE}. File may be empty or corrupted.") + return [] + +@auth.require_admin +def save_data(data): + with open(DATA_FILE, 'w') as f: + json.dump(data, f, indent=4) + +@auth.require_admin +def folder_secret_config_editor(): + return render_template('folder_secret_config_editor.html', alphabet=ALPHABET, admin_enabled=auth.is_admin()) + +@auth.require_admin +def folder_secret_config_data(): + return jsonify(load_data()) + +@auth.require_admin +def folder_secret_config_action(): + p = request.get_json() + data = load_data() + action = p.get('action') + if action == 'delete': + data = [r for r in data if r['secret'] != p['secret']] + elif action == 'update': + old = p['oldSecret']; new = p['newSecret'] + for i, r in enumerate(data): + if r['secret'] == old: + r['secret'] = new + r['validity'] = datetime.strptime(p['validity'], '%Y-%m-%d').strftime('%d.%m.%Y') + r['folders'] = p['folders'] + break + else: + # append if not found + data.append({ + 'secret': new, + 'validity': datetime.strptime(p['validity'], '%Y-%m-%d').strftime('%d.%m.%Y'), + 'folders': p['folders'] + }) + save_data(data) + return jsonify(success=True) + diff --git a/templates/app.html b/templates/app.html index 767b99e..2838b00 100644 --- a/templates/app.html +++ b/templates/app.html @@ -55,6 +55,11 @@ + {% if admin_enabled %} +
+ admin +
+ {% endif %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..52550cd --- /dev/null +++ b/templates/base.html @@ -0,0 +1,71 @@ + + + + + + + {% block title %}Meine Links{% endblock %} + + + + + + {% block head_extra %}{% endblock %} + + + + + + + + {% block content %}{% endblock %} + + {% block scripts %}{% endblock %} + + + + diff --git a/templates/connections.html b/templates/connections.html index 3573830..6daa255 100644 --- a/templates/connections.html +++ b/templates/connections.html @@ -1,218 +1,139 @@ - - - - - - Recent Connections - - - - - - +{# templates/connections.html #} +{% extends 'base.html' %} -
-
- - - - - - - - - - - - - - - -
TimestampLocationUser AgentFile PathFile SizeMIME-TypCached
-
+{% block title %}Recent Connections{% endblock %} + +{% block nav_brand %}Downloads der letzten 10 Minuten{% endblock %} + +{% block nav_extra %} + + Anzahl Verbindungen: 0 + +{% endblock %} + +{% block head_extra %} + +{% endblock %} + +{% block content %} +
+
+ + + + + + + + + + + + + + {# dynamically populated via Socket.IO #} + +
TimestampLocationUser AgentFile PathFile SizeMIME-TypCached
+
+{% endblock %} - - - + - - - + + tbody.innerHTML = ""; + tbody.appendChild(frag); + } + + function animateTableWithNewRow(data) { + const tbody = document.getElementById("connectionsTableBody"); + const currentTs = new Set([...tbody.children].map(r => r.dataset.timestamp)); + const newRecs = data.filter(r => !currentTs.has(r.timestamp)); + + if (newRecs.length) { + const temp = createRow(newRecs[0], false); + temp.style.visibility = "hidden"; + tbody.appendChild(temp); + const h = temp.getBoundingClientRect().height; + temp.remove(); + + tbody.style.transform = `translateY(${h}px)`; + setTimeout(() => { + updateTable(data); + tbody.style.transition = "none"; + tbody.style.transform = "translateY(0)"; + void tbody.offsetWidth; + tbody.style.transition = "transform 0.5s ease-out"; + }, 500); + } else { + updateTable(data); + } + } + + socket.on("recent_connections", data => { + updateStats(data); + animateTableWithNewRow(data); + }); + +{% endblock %} diff --git a/templates/dashboard.html b/templates/dashboard.html index edfa580..51ee5fd 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1,42 +1,14 @@ - - - - - - Dashboard - Verbindungsanalyse - - - - - - +{# templates/dashboard.html #} +{% extends 'base.html' %} + +{# page title #} +{% block title %}Dashboard{% endblock %} + +{# override navbar text: #} +{% block nav_brand %}Auswertung-Downloads{% endblock %} + +{# page content #} +{% block content %}
@@ -271,8 +243,9 @@
+ {% endblock %} - + {% block scripts %} - - +{% endblock %} diff --git a/templates/folder_secret_config_editor.html b/templates/folder_secret_config_editor.html new file mode 100644 index 0000000..5d171b0 --- /dev/null +++ b/templates/folder_secret_config_editor.html @@ -0,0 +1,187 @@ +{# templates/mylinks.html #} +{% extends 'base.html' %} + +{# page title #} +{% block title %}Edit Folder Config{% endblock %} + +{# override navbar text: #} +{% block nav_brand %}Folder Config Editor{% endblock %} + +{# page content #} +{% block content %} +
+

Edit Folder Config

+
+ +
+ + + +{% endblock %} + +{% block scripts %} + + + + + {% endblock %} diff --git a/templates/mylinks.html b/templates/mylinks.html index 9400509..7c534d3 100644 --- a/templates/mylinks.html +++ b/templates/mylinks.html @@ -1,123 +1,105 @@ - - - - - - Meine Links - - - - - - - +{# templates/mylinks.html #} +{% extends 'base.html' %} -
-
- {% if valid_secrets %} - {% for secret in valid_secrets %} -
-
- QR Code for secret -
-
Geheimnis: {{ secret }}
-

- Gültig bis: {{ secret_valid_to[secret] }} -

- Link öffnen - -
-
- - -
- {% if secret_folders[secret] %} -
Ordner
-
    - {% for folder in secret_folders[secret] %} -
  • - {{ folder.foldername }} -
  • - {% endfor %} -
- {% else %} -

Keine Ordner für dieses Gemeimnis hinterlegt.

- {% endif %} -
-
+{# page title #} +{% block title %}Meine Links{% endblock %} + +{# override navbar text: #} +{# +{% block nav_brand %} + Übersicht deiner gültigen Links +{% endblock %} +#} + +{# page‐specific content #} +{% block content %} +
+
+ {% if valid_secrets %} + {% for secret in valid_secrets %} +
+
+ QR Code for secret +
+
Geheimnis: {{ secret }}
+

+ Gültig bis: {{ secret_valid_to[secret] }} +

+ Link öffnen + +
+
+ + +
+ {% if secret_folders[secret] %} +
Ordner
+
    + {% for folder in secret_folders[secret] %} +
  • + {{ folder.foldername }} +
  • + {% endfor %} +
+ {% else %} +

Keine Ordner für dieses Gemeimnis hinterlegt.

+ {% endif %}
- {% endfor %} - {% endif %} - {% if valid_tokens %} - {% for token in valid_tokens %} -
-
- QR Code for token -
-
Token-Link:
-

- Gültig bis: {{ token_valid_to[token] }} -

- Link öffnen - -
-
- - -
- {% if token_folders[token] %} -
Ordner
-
    - {% for folder in token_folders[token] %} -
  • - {{ folder.foldername }} -
  • - {% endfor %} -
- {% else %} -

Keine Ordner für dieses Gemeimnis hinterlegt.

- {% endif %} -
-
-
- {% endfor %} - {% endif %} - {% if not valid_secrets and not valid_tokens %} - - {% endif %} +
+ {% endfor %} + {% endif %} + + {% if valid_tokens %} + {% for token in valid_tokens %} +
+
+ QR Code for token +
+
Token-Link:
+

+ Gültig bis: {{ token_valid_to[token] }} +

+ Link öffnen + +
+
+ + +
+ {% if token_folders[token] %} +
Ordner
+
    + {% for folder in token_folders[token] %} +
  • + {{ folder.foldername }} +
  • + {% endfor %} +
+ {% else %} +

Keine Ordner für dieses Gemeimnis hinterlegt.

+ {% endif %} +
+
+
+ {% endfor %} + {% endif %} + + {% if not valid_secrets and not valid_tokens %} + -
- - - + {% endif %} +
+
+{% endblock %} diff --git a/templates/songs_dashboard.html b/templates/songs_dashboard.html index 5a7936d..f5aa56e 100644 --- a/templates/songs_dashboard.html +++ b/templates/songs_dashboard.html @@ -1,42 +1,14 @@ - - - - - - Dashboard - Verbindungsanalyse - - - - - - +{# templates/songs_dashboard.html #} +{% extends 'base.html' %} + +{# page title #} +{% block title %}Edit Folder Config{% endblock %} + +{# override navbar text: #} +{% block nav_brand %}Dashboard - Analyse Wiederholungen{% endblock %} + +{# page content #} +{% block content %}
@@ -180,3 +152,4 @@ +{% endblock %} \ No newline at end of file