fix analytics

This commit is contained in:
lelo 2025-03-23 12:55:49 +00:00
parent e0730f1ba3
commit 99b0b8b7db
5 changed files with 114 additions and 12 deletions

View File

@ -39,7 +39,7 @@ def shorten_referrer(url):
segment_decoded = unquote(segment) segment_decoded = unquote(segment)
return segment_decoded return segment_decoded
def log_file_access(full_path): def log_file_access(full_path, ip_address, user_agent, referrer):
""" """
Log file access details to a SQLite database. 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 referrer.
@ -61,9 +61,6 @@ def log_file_access(full_path):
''') ''')
# Gather information from the request # Gather information from the request
timestamp = datetime.now().isoformat() timestamp = datetime.now().isoformat()
ip_address = request.remote_addr
user_agent = request.headers.get('User-Agent')
referrer = request.headers.get('Referer')
# Insert the access record into the database # Insert the access record into the database
cursor.execute(''' cursor.execute('''

21
app.py
View File

@ -17,7 +17,7 @@ from urllib.parse import urlparse, unquote
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
from auth import require_secret import auth
import analytics as a import analytics as a
cache = diskcache.Cache('./filecache', size_limit= 48 * 1024**3) # 48 GB limit cache = diskcache.Cache('./filecache', size_limit= 48 * 1024**3) # 48 GB limit
@ -33,6 +33,7 @@ if os.environ.get('FLASK_ENV') == 'production':
app.add_url_rule('/dashboard', view_func=a.dashboard) app.add_url_rule('/dashboard', view_func=a.dashboard)
app.add_url_rule('/network', view_func=a.network) app.add_url_rule('/network', view_func=a.network)
app.add_url_rule('/overview', view_func=auth.overview)
socketio = SocketIO(app, async_mode='eventlet') socketio = SocketIO(app, async_mode='eventlet')
background_thread_running = False background_thread_running = False
@ -154,7 +155,7 @@ def serve_sw():
# API endpoint for AJAX: returns JSON for a given directory. # API endpoint for AJAX: returns JSON for a given directory.
@app.route('/api/path/', defaults={'subpath': ''}) @app.route('/api/path/', defaults={'subpath': ''})
@app.route('/api/path/<path:subpath>') @app.route('/api/path/<path:subpath>')
@require_secret @auth.require_secret
def api_browse(subpath): def api_browse(subpath):
if subpath == '': # root directory if subpath == '': # root directory
foldernames = [] foldernames = []
@ -184,7 +185,7 @@ def api_browse(subpath):
}) })
@app.route("/media/<path:subpath>") @app.route("/media/<path:subpath>")
@require_secret @auth.require_secret
def serve_file(subpath): def serve_file(subpath):
root, *relative_parts = subpath.split('/') root, *relative_parts = subpath.split('/')
base_path = session['folders'][root] base_path = session['folders'][root]
@ -206,7 +207,13 @@ def serve_file(subpath):
range_header = request.headers.get('Range') range_header = request.headers.get('Range')
# only request with starting from the beginning of the file will be tracked # only request with starting from the beginning of the file will be tracked
if request.method != 'HEAD' and (not range_header or range_header.startswith("bytes=0-")): if request.method != 'HEAD' and (not range_header or range_header.startswith("bytes=0-")):
threading.Thread(target=a.log_file_access, args=(full_path,)).start() 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)
).start()
# Check cache first (using diskcache) # Check cache first (using diskcache)
response = None response = None
@ -246,7 +253,7 @@ def serve_file(subpath):
@app.route("/transcript/<path:subpath>") @app.route("/transcript/<path:subpath>")
@require_secret @auth.require_secret
def get_transcript(subpath): def get_transcript(subpath):
root, *relative_parts = subpath.split('/') root, *relative_parts = subpath.split('/')
@ -261,7 +268,7 @@ def get_transcript(subpath):
return content, 200, {'Content-Type': 'text/markdown; charset=utf-8'} return content, 200, {'Content-Type': 'text/markdown; charset=utf-8'}
@app.route("/crawl/<path:subpath>") @app.route("/crawl/<path:subpath>")
@require_secret @auth.require_secret
def crawl_and_cache(subpath): def crawl_and_cache(subpath):
""" """
Crawls through a directory and caches each file. Crawls through a directory and caches each file.
@ -376,7 +383,7 @@ def handle_request_initial_data():
# Catch-all route to serve the single-page application template. # Catch-all route to serve the single-page application template.
@app.route('/', defaults={'path': ''}) @app.route('/', defaults={'path': ''})
@app.route('/<path:path>') @app.route('/<path:path>')
@require_secret @auth.require_secret
def index(path): def index(path):
return render_template("app.html") return render_template("app.html")

37
auth.py
View File

@ -1,7 +1,10 @@
from flask import Flask, render_template, request, session from flask import Flask, render_template, request, session
from functools import wraps from functools import wraps
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
import io
import json import json
import qrcode
import base64
folder_config = {} folder_config = {}
@ -74,3 +77,37 @@ def require_secret(f):
return render_template('index.html') return render_template('index.html')
return decorated_function return decorated_function
@require_secret
def overview():
allowed_secrets = session.get('allowed_secrets', [])
host = request.host
secret_qr_codes = {}
secret_folders = {}
# Build a QR code for each secret (using the URL with the secret as query parameter)
for secret in allowed_secrets:
url = f"https://{host}?secret={secret}"
qr = qrcode.QRCode(version=1, box_size=10, border=4)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = io.BytesIO()
img.save(buffer, format="PNG")
buffer.seek(0)
img_base64 = base64.b64encode(buffer.getvalue()).decode('ascii')
secret_qr_codes[secret] = img_base64
# Lookup folder info for this secret from the global folder_config.
config_item = next((c for c in folder_config if c['secret'] == secret), None)
if config_item:
secret_folders[secret] = config_item['folders']
else:
secret_folders[secret] = []
return render_template('overview.html',
allowed_secrets=allowed_secrets,
secret_qr_codes=secret_qr_codes,
secret_folders=secret_folders)

View File

@ -1,6 +1,7 @@
flask flask
flask_socketio flask_socketio
pillow pillow
qrcode
diskcache diskcache
geoip2 geoip2
gunicorn gunicorn

60
templates/overview.html Normal file
View File

@ -0,0 +1,60 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Übersicht gültiger Verbindungen</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.qr-code {
max-width: 300px;
display: block;
margin: auto;
}
</style>
</head>
<body>
<div class="container my-5">
<h1 class="mb-4">Übersicht deiner gültigen Verbindungen</h1>
{% if allowed_secrets %}
<div class="row">
{% for secret in allowed_secrets %}
<div class="col-md-4 mb-4">
<div class="card h-100 shadow-sm">
<img src="data:image/png;base64,{{ secret_qr_codes[secret] }}" class="card-img-top qr-code p-3" alt="QR Code for secret">
<div class="card-body">
<h5 class="card-title">Geheimnis: {{ secret }}</h5>
<p class="card-text">
<a href="https://{{ request.host }}?secret={{ secret }}" class="text-decoration-none">
https://{{ request.host }}?secret={{ secret }}
</a>
</p>
{% if secret_folders[secret] %}
<h6 class="mt-3">Ordner</h6>
<ul class="list-group list-group-flush">
{% for folder in secret_folders[secret] %}
<li class="list-group-item">
<strong>{{ folder.foldername }}</strong>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">No folders available for this secret.</p>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-warning" role="alert">
No valid secrets found.
</div>
{% endif %}
</div>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>