From 5b4390b6b70556d54d63c0a09ea44a6b30bbe26b Mon Sep 17 00:00:00 2001 From: lelo Date: Tue, 25 Mar 2025 20:54:49 +0000 Subject: [PATCH 01/20] start dynamic structure in settings --- .gitignore | 3 ++- app.py | 4 +++- docker-compose.yml | 20 +++++++++++--------- templates/app.html | 4 ++-- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 3e102dc..56465c8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /filecache /__pycache__ /access_log.db -/folder_config.json \ No newline at end of file +/folder_config.json +/.env \ No newline at end of file diff --git a/app.py b/app.py index d3da61f..326aa35 100755 --- a/app.py +++ b/app.py @@ -377,7 +377,9 @@ def handle_request_initial_data(): @app.route('/') @auth.require_secret def index(path): - return render_template("app.html") + title_short = os.environ.get('TITLE_SHORT', 'Default Title') + title_long = os.environ.get('TITLE_LONG', 'Default Title') + return render_template("app.html", title_short=title_short, title_long=title_long) if __name__ == '__main__': socketio.run(app, debug=True, host='0.0.0.0') diff --git a/docker-compose.yml b/docker-compose.yml index b1a0c62..b7c98ee 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: flask-app: image: python:3.11-slim - container_name: bethaus-app + container_name: "${CONTAINER_NAME}" restart: always working_dir: /app volumes: @@ -17,25 +17,27 @@ services: environment: - FLASK_APP=app.py - FLASK_ENV=production + - TITLE_SHORT=${TITLE_SHORT} + - TITLE_LONG=${TITLE_LONG} networks: - traefik labels: - "traefik.enable=true" # HTTP router (port 80), redirecting to HTTPS - - "traefik.http.routers.bethaus-app.rule=Host(`app.bethaus-speyer.de`)" - - "traefik.http.routers.bethaus-app.entrypoints=web" - - "traefik.http.routers.bethaus-app.middlewares=redirect-to-https" + - "traefik.http.routers.${CONTAINER_NAME}.rule=${HOST_RULE}" + - "traefik.http.routers.${CONTAINER_NAME}.entrypoints=web" + - "traefik.http.routers.${CONTAINER_NAME}.middlewares=redirect-to-https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" # HTTPS router (TLS via Let's Encrypt) - - "traefik.http.routers.bethaus-app-secure.rule=Host(`app.bethaus-speyer.de`)" - - "traefik.http.routers.bethaus-app-secure.entrypoints=websecure" - - "traefik.http.routers.bethaus-app-secure.tls=true" - - "traefik.http.routers.bethaus-app-secure.tls.certresolver=myresolver" + - "traefik.http.routers.${CONTAINER_NAME}-secure.rule=${HOST_RULE}" + - "traefik.http.routers.${CONTAINER_NAME}-secure.entrypoints=websecure" + - "traefik.http.routers.${CONTAINER_NAME}-secure.tls=true" + - "traefik.http.routers.${CONTAINER_NAME}-secure.tls.certresolver=myresolver" # Internal port - - "traefik.http.services.bethaus-app.loadbalancer.server.port=5000" + - "traefik.http.services.${CONTAINER_NAME}.loadbalancer.server.port=5000" # Production-ready Gunicorn command with eventlet command: > diff --git a/templates/app.html b/templates/app.html index 52ab32d..6e81a17 100644 --- a/templates/app.html +++ b/templates/app.html @@ -8,7 +8,7 @@ - Gottesdienste + {{ title_short }} @@ -38,7 +38,7 @@ -

Gottesdienste Speyer und Schwegenheim

+

{{ title_long }}

From 29c2e0a941b65a481689c4b084f8adbf062eb493 Mon Sep 17 00:00:00 2001 From: lelo Date: Tue, 25 Mar 2025 21:48:35 +0000 Subject: [PATCH 02/20] fix: lost selection --- static/app.js | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/static/app.js b/static/app.js index 37a76d9..982ef7e 100644 --- a/static/app.js +++ b/static/app.js @@ -1,6 +1,7 @@ // Define global variables to track music files and the current index. let currentMusicFiles = []; // Array of objects with at least { path, index } let currentMusicIndex = -1; // Index of the current music file +let currentTrackPath = ""; // Helper function: decode each segment then re-encode to avoid double encoding. function encodeSubpath(subpath) { @@ -15,6 +16,21 @@ function encodeSubpath(subpath) { let currentGalleryImages = []; +function paintFile() { + // Highlight the currently playing file + if (currentTrackPath) { + const currentMusicFile = currentMusicFiles.find(file => file.path === currentTrackPath); + if (currentMusicFile) { + const currentMusicFileElement = document.querySelector(`.play-file[data-url="${currentMusicFile.path}"]`); + if (currentMusicFileElement) { + currentMusicFileElement.closest('.file-item').classList.add('currently-playing'); + } + } else { + console.log('Current track not found in the updated list.'); + } + } + } + function renderContent(data) { // Render breadcrumbs, directories (grid view when appropriate), and files. @@ -62,9 +78,7 @@ function renderContent(data) { symbol = '🖼️'; } const indexAttr = file.file_type === 'music' ? ` data-index="${currentMusicFiles.length - 1}"` : ''; - // preserve currently-playing class during reloads - const isCurrentlyPlaying = file.file_type === 'music' && currentMusicIndex === currentMusicFiles.length - 1 ? ' currently-playing' : ''; - contentHTML += `
  • + contentHTML += `
  • ${symbol} ${file.name.replace('.mp3', '')}`; if (file.has_transcript) { contentHTML += `📄`; @@ -132,6 +146,7 @@ function loadDirectory(subpath) { .then(response => response.json()) .then(data => { renderContent(data); + paintFile(); return data; // return data for further chaining }) .catch(error => { @@ -250,6 +265,7 @@ document.querySelectorAll('.play-file').forEach(link => { audioPlayer.src = mediaUrl; audioPlayer.load(); await audioPlayer.play(); + currentTrackPath = relUrl; playerButton.innerHTML = pauseIcon; // Process file path for display. @@ -412,8 +428,6 @@ if ('mediaSession' in navigator) { } document.getElementById('globalAudio').addEventListener('ended', () => { - // Save the current track's path (if any) - const currentTrackPath = currentMusicFiles[currentMusicIndex] ? currentMusicFiles[currentMusicIndex].path : null; reloadDirectory().then(() => { @@ -436,7 +450,7 @@ document.getElementById('globalAudio').addEventListener('ended', () => { }); }); -document.addEventListener("DOMContentLoaded", function() { - // Automatically reload every 5 minutes (300,000 milliseconds) - setInterval(reloadDirectory, 300000); -}); \ No newline at end of file +// document.addEventListener("DOMContentLoaded", function() { +// // Automatically reload every 5 minutes (300,000 milliseconds) +// setInterval(reloadDirectory, 300000); +// }); \ No newline at end of file From b348b4444f11535660732076bc856c71c41358c9 Mon Sep 17 00:00:00 2001 From: lelo Date: Tue, 25 Mar 2025 21:50:47 +0000 Subject: [PATCH 03/20] remove debug from dev --- static/app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/app.js b/static/app.js index 982ef7e..415b06a 100644 --- a/static/app.js +++ b/static/app.js @@ -25,8 +25,6 @@ function paintFile() { if (currentMusicFileElement) { currentMusicFileElement.closest('.file-item').classList.add('currently-playing'); } - } else { - console.log('Current track not found in the updated list.'); } } } From d2dedf51c7953663056dde8e839893e340018e02 Mon Sep 17 00:00:00 2001 From: lelo Date: Wed, 26 Mar 2025 17:52:16 +0000 Subject: [PATCH 04/20] allow all file types --- app.py | 52 +++++++++++++++++++++++---------------------- check_ssh_tunnel.sh | 6 ++---- static/app.js | 3 +++ 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/app.py b/app.py index 326aa35..5418f68 100755 --- a/app.py +++ b/app.py @@ -76,8 +76,8 @@ def list_directory_contents(directory, subpath): transcription_exists = os.path.isdir(transcription_dir) # Define allowed file extensions. - allowed_music_exts = ('.mp3',) - allowed_image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp') + music_exts = ('.mp3',) + image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp') try: with os.scandir(directory) as it: @@ -94,27 +94,31 @@ def list_directory_contents(directory, subpath): directories.append({'name': entry.name, 'path': rel_path.replace(os.sep, '/')}) elif entry.is_file(follow_symlinks=False): lower_name = entry.name.lower() - if lower_name.endswith(allowed_music_exts) or lower_name.endswith(allowed_image_exts): - rel_path = os.path.join(subpath, entry.name) if subpath else entry.name - if lower_name.endswith(allowed_music_exts): - file_type = 'music' - else: - file_type = 'image' - file_entry = {'name': entry.name, 'path': rel_path.replace(os.sep, '/'), 'file_type': file_type} - # Only check for transcription if it's a audio file. - if file_type == 'music' and transcription_exists: - base_name = os.path.splitext(entry.name)[0] - transcript_filename = base_name + '.md' - transcript_path = os.path.join(transcription_dir, transcript_filename) - if os.path.isfile(transcript_path): - file_entry['has_transcript'] = True - transcript_rel_path = os.path.join(subpath, "Transkription", transcript_filename) if subpath else os.path.join("Transkription", transcript_filename) - file_entry['transcript_url'] = url_for('get_transcript', subpath=transcript_rel_path.replace(os.sep, '/')) - else: - file_entry['has_transcript'] = False + + # implement file type filtering here !!! + #if lower_name.endswith(music_exts) or lower_name.endswith(image_exts): + rel_path = os.path.join(subpath, entry.name) if subpath else entry.name + if lower_name.endswith(music_exts): + file_type = 'music' + elif lower_name.endswith(image_exts): + file_type = 'image' + else: + file_type = 'other' + file_entry = {'name': entry.name, 'path': rel_path.replace(os.sep, '/'), 'file_type': file_type} + # Only check for transcription if it's a audio file. + if file_type == 'music' and transcription_exists: + base_name = os.path.splitext(entry.name)[0] + transcript_filename = base_name + '.md' + transcript_path = os.path.join(transcription_dir, transcript_filename) + if os.path.isfile(transcript_path): + file_entry['has_transcript'] = True + transcript_rel_path = os.path.join(subpath, "Transkription", transcript_filename) if subpath else os.path.join("Transkription", transcript_filename) + file_entry['transcript_url'] = url_for('get_transcript', subpath=transcript_rel_path.replace(os.sep, '/')) else: file_entry['has_transcript'] = False - files.append(file_entry) + else: + file_entry['has_transcript'] = False + files.append(file_entry) except PermissionError: pass @@ -191,10 +195,8 @@ def serve_file(subpath): mime, _ = mimetypes.guess_type(full_path) mime = mime or 'application/octet-stream' - if mime and mime.startswith('image/'): - pass # do not log access to images - - else: + # logging only for mp3 + if mime and mime.startswith('audio/mpeg'): # HEAD request are coming in to initiate server caching. # only log initial hits and not the reload of further file parts range_header = request.headers.get('Range') diff --git a/check_ssh_tunnel.sh b/check_ssh_tunnel.sh index 17b4e60..e560d62 100755 --- a/check_ssh_tunnel.sh +++ b/check_ssh_tunnel.sh @@ -16,15 +16,13 @@ SERVER1_MOUNT_POINTS=( "/mnt/Gottesdienste Speyer" "/mnt/Besondere Gottesdienste" "/mnt/Liedersammlung" - "/mnt/Jungschar" - "/mnt/Jugend" + "/mnt/app_share" ) SERVER1_NFS_SHARES=( "/volume1/Aufnahme-stereo/010 Gottesdienste ARCHIV" "/volume1/Aufnahme-stereo/013 Besondere Gottesdienste" "/volume1/Aufnahme-stereo/014 Liedersammlung" - "/volume1/app.share/Jungschar" - "/volume1/app.share/Jugend" + "/volume1/app_share" ) # Server 2 Configuration diff --git a/static/app.js b/static/app.js index 415b06a..2ed3b39 100644 --- a/static/app.js +++ b/static/app.js @@ -300,6 +300,9 @@ document.querySelectorAll('.play-file').forEach(link => { } else if (fileType === 'image') { // Open the gallery modal for image files. openGalleryModal(relUrl); + } else { + // serve like a download + window.location.href = `/media/${relUrl}`; } }); }); From 2b58bd74cc5baf6890e1e4d95cd5f02736a3dc65 Mon Sep 17 00:00:00 2001 From: lelo Date: Wed, 26 Mar 2025 18:14:08 +0000 Subject: [PATCH 05/20] fix centered directories --- static/app.css | 4 ++-- static/app.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/app.css b/static/app.css index 969105c..66219ad 100644 --- a/static/app.css +++ b/static/app.css @@ -102,7 +102,7 @@ div.directory-item a, li.directory-item a, li.file-item a { } .directories-grid .directory-item { background-color: #fff; - padding: 15px 10px; + padding: 15px; border-radius: 5px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); @@ -114,7 +114,7 @@ div.directory-item a, li.directory-item a, li.file-item a { grid-template-columns: 1fr auto; align-items: center; margin: 10px 0; - padding: 15px 10px; + padding: 15px; background-color: #fff; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); diff --git a/static/app.js b/static/app.js index 2ed3b39..503b273 100644 --- a/static/app.js +++ b/static/app.js @@ -46,8 +46,8 @@ function renderContent(data) { let contentHTML = ''; if (data.directories.length > 0) { contentHTML += '
      '; - // Check if every directory name is short (≤15 characters) - const areAllShort = data.directories.every(dir => dir.name.length <= 15); + // Check if every directory name is short (≤15 characters) and no files are present + const areAllShort = data.directories.every(dir => dir.name.length <= 15) && data.files.length === 0; if (areAllShort) { contentHTML += '
      '; data.directories.forEach(dir => { From 531e728b13043bc701d00f0566237379b61a31d6 Mon Sep 17 00:00:00 2001 From: lelo Date: Wed, 26 Mar 2025 22:43:39 +0000 Subject: [PATCH 06/20] add device_id --- analytics.py | 68 +++++++++++++++++++++------------------- app.py | 3 +- auth.py | 5 +++ templates/dashboard.html | 18 +++++------ 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/analytics.py b/analytics.py index 0d2e500..203441d 100644 --- a/analytics.py +++ b/analytics.py @@ -32,17 +32,10 @@ def get_device_type(user_agent): else: return 'Other' -def shorten_referrer(url): - segments = [seg for seg in url.split('/') if seg] - segment = segments[-1] - # Decode all percent-encoded characters (like %20, %2F, etc.) - segment_decoded = unquote(segment) - return segment_decoded - -def log_file_access(full_path, ip_address, user_agent, referrer): +def log_file_access(rel_path, ip_address, user_agent, device_id): """ Log file access details to a SQLite database. - Records the timestamp, full file path, client IP, user agent, and referrer. + Records the timestamp, full file path, client IP, user agent, and device_id. """ global file_access_temp # Connect to the database (this will create the file if it doesn't exist) @@ -53,10 +46,10 @@ def log_file_access(full_path, ip_address, user_agent, referrer): CREATE TABLE IF NOT EXISTS file_access_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, - full_path TEXT, + rel_path TEXT, ip_address TEXT, user_agent TEXT, - referrer TEXT + device_id TEXT ) ''') # Gather information from the request @@ -64,12 +57,12 @@ def log_file_access(full_path, ip_address, user_agent, referrer): # Insert the access record into the database cursor.execute(''' - INSERT INTO file_access_log (timestamp, full_path, ip_address, user_agent, referrer) + INSERT INTO file_access_log (timestamp, rel_path, ip_address, user_agent, device_id) VALUES (?, ?, ?, ?, ?) - ''', (timestamp, full_path, ip_address, user_agent, referrer)) + ''', (timestamp, rel_path, ip_address, user_agent, device_id)) conn.commit() conn.close() - file_access_temp.insert(0, [timestamp, full_path, ip_address, user_agent, referrer]) + file_access_temp.insert(0, [timestamp, rel_path, ip_address, user_agent, device_id]) return return_file_access() def return_file_access(): @@ -111,10 +104,10 @@ def dashboard(): # Raw file access counts for the table (top files) cursor.execute(''' - SELECT full_path, COUNT(*) as access_count + SELECT rel_path, COUNT(*) as access_count FROM file_access_log WHERE timestamp >= ? - GROUP BY full_path + GROUP BY rel_path ORDER BY access_count DESC LIMIT 20 ''', (start.isoformat(),)) @@ -132,14 +125,14 @@ def dashboard(): # Top files for bar chart cursor.execute(''' - SELECT full_path, COUNT(*) as access_count + SELECT rel_path, COUNT(*) as access_count FROM file_access_log WHERE timestamp >= ? - GROUP BY full_path + GROUP BY rel_path ORDER BY access_count DESC LIMIT 10 ''', (start.isoformat(),)) - top_files_data = [dict(full_path=row[0], access_count=row[1]) for row in cursor.fetchall()] + top_files_data = [dict(rel_path=row[0], access_count=row[1]) for row in cursor.fetchall()] # User agent distribution (aggregate by device type) cursor.execute(''' @@ -157,20 +150,29 @@ def dashboard(): # Rename to user_agent_data for compatibility with the frontend user_agent_data = [dict(device=device, count=count) for device, count in device_counts.items()] - # Referrer distribution (shorten links) + # Parent folder distribution cursor.execute(''' - SELECT referrer, COUNT(*) as count + SELECT rel_path, COUNT(*) as count FROM file_access_log WHERE timestamp >= ? - GROUP BY referrer + GROUP BY rel_path ORDER BY count DESC - LIMIT 10 ''', (start.isoformat(),)) - referrer_data = [] + folder_data = {} for row in cursor.fetchall(): - raw_ref = row[0] - shortened = shorten_referrer(raw_ref) if raw_ref else "Direct/None" - referrer_data.append(dict(referrer=shortened, count=row[1])) + rel_path = row[0] + parent_folder = rel_path.rsplit('/', 1)[0] if '/' in rel_path else "Root" + folder_data[parent_folder] = folder_data.get(parent_folder, 0) + row[1] + + # Convert the dictionary to a list of dictionaries + folder_data = [ + dict(folder=folder, count=count) + for folder, count in folder_data.items() + ] + + # Sort by count in descending order and take the top 10 + folder_data.sort(key=lambda x: x['count'], reverse=True) + folder_data = folder_data[:10] # Aggregate IP addresses with counts cursor.execute(''' @@ -208,13 +210,13 @@ def dashboard(): cursor.execute('SELECT COUNT(*) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),)) total_accesses = cursor.fetchone()[0] - # Use a separate query to count unique files (distinct full_path values) - cursor.execute('SELECT COUNT(DISTINCT full_path) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),)) + # Use a separate query to count unique files (distinct rel_path values) + cursor.execute('SELECT COUNT(DISTINCT rel_path) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),)) unique_files = cursor.fetchone()[0] # Use a separate query to count unique IP addresses - cursor.execute('SELECT COUNT(DISTINCT ip_address) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),)) - unique_ips = cursor.fetchone()[0] + cursor.execute('SELECT COUNT(DISTINCT device_id) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),)) + unique_user = cursor.fetchone()[0] conn.close() @@ -224,8 +226,8 @@ def dashboard(): daily_access_data=daily_access_data, top_files_data=top_files_data, user_agent_data=user_agent_data, - referrer_data=referrer_data, + folder_data=folder_data, location_data=location_data, total_accesses=total_accesses, unique_files=unique_files, - unique_ips=unique_ips) \ No newline at end of file + unique_user=unique_user) \ No newline at end of file diff --git a/app.py b/app.py index 5418f68..953a3e1 100755 --- a/app.py +++ b/app.py @@ -205,10 +205,9 @@ def serve_file(subpath): if request.method == 'GET' and (not range_header or (range_header.startswith("bytes=0-") and range_header != "bytes=0-1")): ip_address = request.remote_addr user_agent = request.headers.get('User-Agent') - referrer = request.headers.get('Referer') threading.Thread( target=a.log_file_access, - args=(full_path, ip_address, user_agent, referrer) + args=(subpath, ip_address, user_agent, session['device_id']) ).start() # Check cache first (using diskcache) diff --git a/auth.py b/auth.py index 0118009..182746c 100644 --- a/auth.py +++ b/auth.py @@ -2,6 +2,7 @@ from flask import Flask, render_template, request, redirect, url_for, session from functools import wraps from datetime import datetime, date, timedelta import io +import os import json import qrcode import base64 @@ -72,6 +73,10 @@ def require_secret(f): # 6) If we have folders, proceed; otherwise show index if session['folders']: + # assume since visitor has a valid secret, they are ok with annonymous tracking + # this is required to track the devices connecting over the same ip address + if 'device_id' not in session: + session['device_id'] = os.urandom(32).hex() return f(*args, **kwargs) else: return render_template('index.html') diff --git a/templates/dashboard.html b/templates/dashboard.html index e5f0dff..eb7b7ec 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -58,7 +58,7 @@
      eindeutige Nutzer
      -

      {{ unique_ips }}

      +

      {{ unique_user }}

      @@ -93,12 +93,12 @@
  • - +
    Verteilung auf Ordner
    - +
    @@ -174,7 +174,7 @@ const topFilesData = {{ top_files_data|tojson }}; // Note: user_agent_data now contains 'device' and 'count' const userAgentData = {{ user_agent_data|tojson }}; - const referrerData = {{ referrer_data|tojson }}; + const folderData = {{ folder_data|tojson }}; // Access Trend Chart - Line Chart const ctxTrend = document.getElementById('accessTrendChart').getContext('2d'); @@ -235,14 +235,14 @@ options: { responsive: true } }); - // Referrer Distribution - Pie Chart (with shortened referrers) - const ctxReferrer = document.getElementById('referrerChart').getContext('2d'); - new Chart(ctxReferrer, { + // folder Distribution - Pie Chart (with shortened folders) + const ctxfolder = document.getElementById('folderChart').getContext('2d'); + new Chart(ctxfolder, { type: 'pie', data: { - labels: referrerData.map(item => item.referrer), + labels: folderData.map(item => item.folder), datasets: [{ - data: referrerData.map(item => item.count) + data: folderData.map(item => item.count) }] }, options: { responsive: true } From 21016695677341ee90616eadf0c4d6374503dff7 Mon Sep 17 00:00:00 2001 From: lelo Date: Thu, 27 Mar 2025 23:32:25 +0000 Subject: [PATCH 07/20] transform existing sql data --- transforme_sql.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 transforme_sql.py diff --git a/transforme_sql.py b/transforme_sql.py new file mode 100644 index 0000000..1ef9f9a --- /dev/null +++ b/transforme_sql.py @@ -0,0 +1,42 @@ +import sqlite3 + +def transform_database(db_path): + # Connect to the SQLite database + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 1. Create the new table with the desired schema. + cursor.execute(''' + CREATE TABLE IF NOT EXISTS file_access_log_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, + rel_path TEXT, + ip_address TEXT, + user_agent TEXT, + device_id TEXT + ) + ''') + + # 2. Copy data from the old table, applying the following transformations: + # - Rename full_path to rel_path and remove '/mnt/' from its entries. + # - Omit the referrer column. + # - Copy ip_address into the new device_id column. + cursor.execute(''' + INSERT INTO file_access_log_new (id, timestamp, rel_path, ip_address, user_agent, device_id) + SELECT id, timestamp, REPLACE(full_path, '/mnt/', ''), ip_address, user_agent, ip_address + FROM file_access_log + ''') + + # 3. Drop the old table. + cursor.execute('DROP TABLE file_access_log') + + # 4. Rename the new table to use the original table's name. + cursor.execute('ALTER TABLE file_access_log_new RENAME TO file_access_log') + + # Commit the changes and close the connection. + conn.commit() + conn.close() + +if __name__ == "__main__": + # Replace 'your_database.db' with the path to your SQLite database file. + transform_database("access_log.db") From 788cf1352ee8264fcfcb256d29f2e3c847b350c1 Mon Sep 17 00:00:00 2001 From: lelo Date: Sat, 29 Mar 2025 08:48:23 +0000 Subject: [PATCH 08/20] soft mount for nfs to prevent unresponsive app --- check_ssh_tunnel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check_ssh_tunnel.sh b/check_ssh_tunnel.sh index e560d62..4277192 100755 --- a/check_ssh_tunnel.sh +++ b/check_ssh_tunnel.sh @@ -122,7 +122,7 @@ for server in "${SERVERS[@]}"; do # Mount the NFS share if it's not already mounted. if ! is_nfs_mounted "${MOUNT_POINT}"; then echo "[INFO] NFS share is not mounted at ${MOUNT_POINT}. Attempting to mount..." - sudo mount -t nfs -o port="${LOCAL_PORT}",nolock,soft 127.0.0.1:"${NFS_SHARE}" "${MOUNT_POINT}" + sudo mount -t nfs -o ro,port="${LOCAL_PORT}",nolock,soft,timeo=5,retrans=3 127.0.0.1:"${NFS_SHARE}" "${MOUNT_POINT}" if is_nfs_mounted "${MOUNT_POINT}"; then echo "[SUCCESS] NFS share mounted successfully at ${MOUNT_POINT}." else From 2ee5cf3a2655c9a172c37bc7cdcb9dd07107d6b8 Mon Sep 17 00:00:00 2001 From: lelo Date: Sun, 30 Mar 2025 19:21:52 +0000 Subject: [PATCH 09/20] add hourly bar diagram --- analytics.py | 52 ++++++++++++++++++++++++++++--------- templates/dashboard.html | 55 ++++++++++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/analytics.py b/analytics.py index 203441d..9bd6475 100644 --- a/analytics.py +++ b/analytics.py @@ -123,16 +123,44 @@ def dashboard(): ''', (start.isoformat(),)) daily_access_data = [dict(date=row[0], count=row[1]) for row in cursor.fetchall()] - # Top files for bar chart - cursor.execute(''' - SELECT rel_path, COUNT(*) as access_count - FROM file_access_log - WHERE timestamp >= ? - GROUP BY rel_path - ORDER BY access_count DESC - LIMIT 10 - ''', (start.isoformat(),)) - top_files_data = [dict(rel_path=row[0], access_count=row[1]) for row in cursor.fetchall()] + # Aggregate download counts by time bucket according to the timeframe. + if timeframe == 'today': + # Group by hour (0-23) + cursor.execute(''' + SELECT strftime('%H', timestamp) as bucket, COUNT(*) as count + FROM file_access_log + WHERE timestamp >= ? + GROUP BY bucket + ORDER BY bucket + ''', (start.isoformat(),)) + elif timeframe in ('7days', '30days'): + # Group by day (YYYY-MM-DD) + cursor.execute(''' + SELECT date(timestamp) as bucket, COUNT(*) as count + FROM file_access_log + WHERE timestamp >= ? + GROUP BY bucket + ORDER BY bucket + ''', (start.isoformat(),)) + elif timeframe == '365days': + # Group by month (YYYY-MM) + cursor.execute(''' + SELECT strftime('%Y-%m', timestamp) as bucket, COUNT(*) as count + FROM file_access_log + WHERE timestamp >= ? + GROUP BY bucket + ORDER BY bucket + ''', (start.isoformat(),)) + else: + # Fallback: group by day + cursor.execute(''' + SELECT date(timestamp) as bucket, COUNT(*) as count + FROM file_access_log + WHERE timestamp >= ? + GROUP BY bucket + ORDER BY bucket + ''', (start.isoformat(),)) + timeframe_data = [dict(bucket=row[0], count=row[1]) for row in cursor.fetchall()] # User agent distribution (aggregate by device type) cursor.execute(''' @@ -224,10 +252,10 @@ def dashboard(): timeframe=timeframe, rows=rows, daily_access_data=daily_access_data, - top_files_data=top_files_data, user_agent_data=user_agent_data, folder_data=folder_data, location_data=location_data, total_accesses=total_accesses, unique_files=unique_files, - unique_user=unique_user) \ No newline at end of file + unique_user=unique_user, + timeframe_data=timeframe_data) \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index eb7b7ec..2aa2175 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -75,12 +75,12 @@ - +
    -
    Häufig geladene Dateien
    - +
    Downloads nach Zeit
    +
    @@ -171,11 +171,41 @@