From ae9f426361ea4addbfad50fa05d1a84e4cbd5f43 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Wed, 10 Sep 2025 17:16:01 -0700 Subject: [PATCH] Add activity logging for key user actions Introduces activity item logging for settings saves, connection tests, auto-detects, authentication flows, searches, batch cancellations, and discovery operations in web_server.py. Updates dashboard connection test API and UI to use a new endpoint and function, ensuring user actions are tracked and surfaced in the activity feed. --- web_server.py | 84 ++++++++++++++++++++++++++++++++++++++++++ webui/index.html | 6 +-- webui/static/script.js | 27 ++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/web_server.py b/web_server.py index 8020602b..821a1604 100644 --- a/web_server.py +++ b/web_server.py @@ -1518,6 +1518,12 @@ def handle_settings(): config_manager.set(f'{service}.{key}', value) print("✅ Settings saved successfully via Web UI.") + + # Add activity for settings save + changed_services = list(new_settings.keys()) + services_text = ", ".join(changed_services) + add_activity_item("⚙️", "Settings Updated", f"{services_text} configuration saved", "Now") + spotify_client._setup_client() plex_client.server = None jellyfin_client.server = None @@ -1553,6 +1559,42 @@ def test_connection_endpoint(): service = active_server # use the actual server name for the test success, message = run_service_test(service, test_config) + + # Add activity for connection test + if success: + add_activity_item("✅", "Connection Test", f"{service.title()} connection successful", "Now") + else: + add_activity_item("❌", "Connection Test", f"{service.title()} connection failed", "Now") + + return jsonify({"success": success, "error": "" if success else message, "message": message if success else ""}) + +@app.route('/api/test-dashboard-connection', methods=['POST']) +def test_dashboard_connection_endpoint(): + """Test connection from dashboard - creates specific dashboard activity items""" + data = request.get_json() + service = data.get('service') + if not service: + return jsonify({"success": False, "error": "No service specified."}), 400 + + print(f"Received dashboard test connection request for: {service}") + + # Get the current settings from the main config manager to test with + test_config = config_manager.get(service, {}) + + # For media servers, the service name might be 'server' + if service == 'server': + active_server = config_manager.get_active_media_server() + test_config = config_manager.get(active_server, {}) + service = active_server # use the actual server name for the test + + success, message = run_service_test(service, test_config) + + # Add activity for dashboard connection test (different from settings test) + if success: + add_activity_item("🎛️", "Dashboard Test", f"{service.title()} service verified", "Now") + else: + add_activity_item("⚠️", "Dashboard Test", f"{service.title()} service check failed", "Now") + return jsonify({"success": success, "error": "" if success else message, "message": message if success else ""}) @app.route('/api/detect-media-server', methods=['POST']) @@ -1560,19 +1602,30 @@ def detect_media_server_endpoint(): data = request.get_json() server_type = data.get('server_type') print(f"Received auto-detect request for: {server_type}") + + # Add activity for auto-detect start + add_activity_item("🔍", "Auto-Detect Started", f"Searching for {server_type} server", "Now") + found_url = run_detection(server_type) if found_url: + add_activity_item("✅", "Auto-Detect Complete", f"{server_type} found at {found_url}", "Now") return jsonify({"success": True, "found_url": found_url}) else: + add_activity_item("❌", "Auto-Detect Failed", f"No {server_type} server found", "Now") return jsonify({"success": False, "error": f"No {server_type} server found on common local addresses."}) @app.route('/api/detect-soulseek', methods=['POST']) def detect_soulseek_endpoint(): print("Received auto-detect request for slskd") + + # Add activity for soulseek auto-detect start + add_activity_item("🔍", "Auto-Detect Started", "Searching for slskd server", "Now") found_url = run_detection('slskd') if found_url: + add_activity_item("✅", "Auto-Detect Complete", f"slskd found at {found_url}", "Now") return jsonify({"success": True, "found_url": found_url}) else: + add_activity_item("❌", "Auto-Detect Failed", "No slskd server found", "Now") return jsonify({"success": False, "error": "No slskd server found on common local addresses."}) # --- Full Tidal Authentication Flow --- @@ -1595,12 +1648,17 @@ def auth_tidal(): print(" tidal_client.authenticate() to start the flow.") print("Please follow the instructions in the console to log in to Tidal.") + # Add activity for authentication start + add_activity_item("🔐", "Tidal Auth Started", "Initiating authentication flow", "Now") + if temp_tidal_client.authenticate(): # Re-initialize the main client instance after successful auth global tidal_client tidal_client = TidalClient() + add_activity_item("✅", "Tidal Auth Complete", "Successfully authenticated with Tidal", "Now") return "

✅ Tidal Authentication Successful!

You can now close this window and return to the SoulSync application.

" else: + add_activity_item("❌", "Tidal Auth Failed", "Authentication with Tidal failed", "Now") return "

❌ Tidal Authentication Failed

Please check the console output of the server for a login URL and follow the instructions.

", 400 @@ -1674,6 +1732,9 @@ def search_music(): print(f"Web UI Search for: '{query}'") + # Add activity for search start + add_activity_item("🔍", "Search Started", f"'{query}'", "Now") + try: tracks, albums = asyncio.run(soulseek_client.search(query)) @@ -1694,6 +1755,10 @@ def search_music(): # Sort by quality score all_results = sorted(processed_albums + processed_tracks, key=lambda x: x.get('quality_score', 0), reverse=True) + # Add activity for search completion + total_results = len(all_results) + add_activity_item("✅", "Search Complete", f"'{query}' - {total_results} results", "Now") + return jsonify({"results": all_results}) except Exception as e: @@ -7970,6 +8035,10 @@ def cancel_batch(batch_id): task['status'] = 'cancelled' cancelled_count += 1 + # Add activity for batch cancellation + playlist_name = download_batches[batch_id].get('playlist_name', 'Unknown Playlist') + add_activity_item("🚫", "Batch Cancelled", f"'{playlist_name}' - {cancelled_count} downloads cancelled", "Now") + print(f"✅ Cancelled batch {batch_id} with {cancelled_count} tasks") return jsonify({"success": True, "cancelled_tasks": cancelled_count}) @@ -8449,6 +8518,9 @@ def start_tidal_discovery(playlist_id): } tidal_discovery_states[playlist_id] = state + # Add activity for discovery start + add_activity_item("🔍", "Tidal Discovery Started", f"'{target_playlist.name}' - {len(target_playlist.tracks)} tracks", "Now") + # Start discovery worker future = tidal_discovery_executor.submit(_run_tidal_discovery_worker, playlist_id) state['discovery_future'] = future @@ -8714,6 +8786,9 @@ def _run_tidal_discovery_worker(playlist_id): state['status'] = 'discovered' state['discovery_progress'] = 100 + # Add activity for discovery completion + add_activity_item("✅", "Tidal Discovery Complete", f"'{playlist.name}' - {successful_discoveries}/{len(playlist.tracks)} tracks found", "Now") + print(f"✅ Tidal discovery complete: {successful_discoveries}/{len(playlist.tracks)} tracks found") except Exception as e: @@ -8993,6 +9068,11 @@ def start_youtube_discovery(url_hash): state['discovery_progress'] = 0 state['spotify_matches'] = 0 + # Add activity for discovery start + playlist_name = state['playlist']['name'] + track_count = len(state['playlist']['tracks']) + add_activity_item("🔍", "YouTube Discovery Started", f"'{playlist_name}' - {track_count} tracks", "Now") + # Start discovery worker future = youtube_discovery_executor.submit(_run_youtube_discovery_worker, url_hash) state['discovery_future'] = future @@ -9146,6 +9226,10 @@ def _run_youtube_discovery_worker(url_hash): state['status'] = 'complete' state['discovery_progress'] = 100 + # Add activity for discovery completion + playlist_name = playlist['name'] + add_activity_item("✅", "YouTube Discovery Complete", f"'{playlist_name}' - {state['spotify_matches']}/{len(tracks)} tracks found", "Now") + print(f"✅ YouTube discovery complete: {state['spotify_matches']}/{len(tracks)} tracks matched") except Exception as e: diff --git a/webui/index.html b/webui/index.html index 359b4e3c..f854521c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -168,7 +168,7 @@

Disconnected

Response: --

@@ -179,7 +179,7 @@

Disconnected

Response: --

@@ -190,7 +190,7 @@

Disconnected

Response: --

diff --git a/webui/static/script.js b/webui/static/script.js index 347bb663..f3eebba2 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -259,6 +259,7 @@ const API = { config: '/config', settings: '/api/settings', testConnection: '/api/test-connection', + testDashboardConnection: '/api/test-dashboard-connection', playlists: '/api/playlists', sync: '/api/sync', search: '/api/search', @@ -1511,6 +1512,32 @@ async function testConnection(service) { } } +// Dashboard-specific test functions that create activity items +async function testDashboardConnection(service) { + try { + showLoadingOverlay(`Testing ${service} service...`); + + const response = await fetch(API.testDashboardConnection, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ service }) + }); + + const result = await response.json(); + + if (result.success) { + showToast(`${service} service verified`, 'success'); + } else { + showToast(`${service} service check failed: ${result.error}`, 'error'); + } + } catch (error) { + console.error(`Error testing ${service} service:`, error); + showToast(`Failed to test ${service} service`, 'error'); + } finally { + hideLoadingOverlay(); + } +} + // Individual Auto-detect functions - same as GUI async function autoDetectPlex() { try {