From b4f47d0cb104cae5f862c397f563f5f2f5b2bc05 Mon Sep 17 00:00:00 2001 From: lelo Date: Wed, 10 Dec 2025 23:45:55 +0000 Subject: [PATCH] add map for connection --- analytics.py | 63 +++++++- app.py | 16 ++ templates/connections.html | 297 +++++++++++++++++++++++++++++++++---- 3 files changed, 343 insertions(+), 33 deletions(-) diff --git a/analytics.py b/analytics.py index 2d9b176..77c4dc7 100644 --- a/analytics.py +++ b/analytics.py @@ -35,9 +35,22 @@ def init_log_db(): country TEXT, user_agent TEXT, device_id TEXT, - cached BOOLEAN + cached BOOLEAN, + latitude REAL, + longitude REAL ) ''') + + # Migrate existing database to add latitude and longitude columns if they don't exist + cursor = log_db.execute("PRAGMA table_info(file_access_log)") + columns = [column[1] for column in cursor.fetchall()] + + if 'latitude' not in columns: + with log_db: + log_db.execute('ALTER TABLE file_access_log ADD COLUMN latitude REAL') + if 'longitude' not in columns: + with log_db: + log_db.execute('ALTER TABLE file_access_log ADD COLUMN longitude REAL') init_log_db() @@ -46,9 +59,11 @@ def lookup_location(ip): response = geoReader.city(ip) country = response.country.name if response.country.name else "Unknown" city = response.city.name if response.city.name else "Unknown" - return city, country + lat = response.location.latitude if response.location.latitude else None + lon = response.location.longitude if response.location.longitude else None + return city, country, lat, lon except Exception: - return "Unknown", "Unknown" + return "Unknown", "Unknown", None, None def get_device_type(user_agent): """Classify device type based on user agent string.""" @@ -108,15 +123,32 @@ def log_file_access(rel_path, filesize, mime, ip_address, user_agent, device_id, now = datetime.now(timezone.utc).astimezone() iso_ts = now.isoformat() - # Convert the IP address to a location - city, country = lookup_location(ip_address) + # Convert the IP address to a location with coordinates + city, country, lat, lon = lookup_location(ip_address) with log_db: log_db.execute(''' INSERT INTO file_access_log - (timestamp, rel_path, filesize, mime, city, country, user_agent, device_id, cached) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', (iso_ts, rel_path, filesize, mime, city, country, user_agent, device_id, cached)) + (timestamp, rel_path, filesize, mime, city, country, user_agent, device_id, cached, latitude, longitude) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (iso_ts, rel_path, filesize, mime, city, country, user_agent, device_id, cached, lat, lon)) + + # Emit real-time connection data via WebSocket + try: + from flask_socketio import SocketIO + # Import socketio from app module to emit the event + import app as app_module + if hasattr(app_module, 'socketio') and lat and lon: + app_module.socketio.emit('new_connection', { + 'timestamp': iso_ts, + 'city': city, + 'country': country, + 'lat': lat, + 'lon': lon + }) + except Exception as e: + # Don't fail the logging if WebSocket emit fails + print(f"WebSocket emit failed: {e}") # Prune temp entries older than 10 minutes cutoff = now - timedelta(minutes=10) @@ -227,6 +259,21 @@ def return_file_access(): return [] +def return_file_access_with_geo(): + """Return recent file access logs with geographic coordinates from the database.""" + cutoff_time = datetime.now(timezone.utc).astimezone() - timedelta(minutes=10) + cutoff_str = cutoff_time.isoformat() + + with log_db: + cursor = log_db.execute(''' + SELECT timestamp, rel_path, filesize, mime, city, country, user_agent, device_id, latitude, longitude + FROM file_access_log + WHERE timestamp >= ? + ORDER BY timestamp DESC + ''', (cutoff_str,)) + return cursor.fetchall() + + def songs_dashboard(): # — SESSION & PARAM HANDLING (unchanged) — if 'songs_dashboard_timeframe' not in session: diff --git a/app.py b/app.py index 7d4baa6..8178790 100755 --- a/app.py +++ b/app.py @@ -673,6 +673,22 @@ def handle_request_initial_data(): ] emit('recent_connections', connections) +@socketio.on('request_map_data') +def handle_request_map_data(): + """Send initial map data with geographic coordinates""" + rows = a.return_file_access_with_geo() + connections = [] + for row in rows: + if row[8] and row[9]: # lat and lon exist + connections.append({ + 'timestamp': row[0], + 'city': row[4], + 'country': row[5], + 'lat': row[8], + 'lon': row[9] + }) + emit('map_initial_data', {'connections': connections}) + # Catch-all route to serve the single-page application template. @app.route('/', defaults={'path': ''}) @app.route('/') diff --git a/templates/connections.html b/templates/connections.html index e89f48c..8e79575 100644 --- a/templates/connections.html +++ b/templates/connections.html @@ -4,9 +4,68 @@ {% block title %}Recent Connections{% endblock %} {% block head_extra %} + + {% endblock %} {% block content %} -
-

Verbindungen der letzten 10 Minuten

- - Anzahl Verbindungen: 0 - -
- - - - - - - - - - - - - - {# dynamically populated via Socket.IO #} - -
TimestampLocationUser AgentFile PathFile SizeMIME-TypCached
+
+ +
+
+

Live Connection Map

+
+
+
Active Dots
+
0
+
+
+
Last Connection
+
-
+
+
+
+
+
+ + +
+
+

Verbindungen der letzten 10 Minuten

+
+
+
Total Connections
+
0
+
+
+
+
+ + + + + + + + + + + + + + {# dynamically populated via Socket.IO #} + +
TimestampLocationUser AgentFile PathFile SizeMIME-TypCached
+
{% endblock %} @@ -54,9 +149,161 @@ {% block scripts %}