Add system stats API and dashboard service status updates
Introduces a new /api/system/stats endpoint in web_server.py to provide system statistics such as uptime, memory usage, download speed, and sync counts. Enhances the dashboard in script.js to periodically fetch and display service connection status and system stats, including response times for Spotify, media server, and Soulseek.
This commit is contained in:
parent
8336109661
commit
135728ce89
2 changed files with 194 additions and 3 deletions
118
web_server.py
118
web_server.py
|
|
@ -1318,23 +1318,131 @@ def get_status():
|
|||
if not all([spotify_client, plex_client, jellyfin_client, soulseek_client, config_manager]):
|
||||
return jsonify({"error": "Core services not initialized."}), 500
|
||||
try:
|
||||
import time
|
||||
|
||||
active_server = config_manager.get_active_media_server()
|
||||
|
||||
# Test Spotify with response time
|
||||
spotify_start = time.time()
|
||||
spotify_status = spotify_client.is_authenticated()
|
||||
spotify_response_time = (time.time() - spotify_start) * 1000 # Convert to ms
|
||||
|
||||
# Test media server with response time
|
||||
media_server_start = time.time()
|
||||
media_server_status = False
|
||||
if active_server == "plex":
|
||||
media_server_status = plex_client.is_connected()
|
||||
elif active_server == "jellyfin":
|
||||
media_server_status = jellyfin_client.is_connected()
|
||||
media_server_response_time = (time.time() - media_server_start) * 1000
|
||||
|
||||
# Test Soulseek (just check if configured, no network test)
|
||||
soulseek_start = time.time()
|
||||
soulseek_status = soulseek_client.is_configured()
|
||||
soulseek_response_time = (time.time() - soulseek_start) * 1000
|
||||
|
||||
status_data = {
|
||||
'spotify': spotify_client.is_authenticated(),
|
||||
'media_server': media_server_status,
|
||||
'soulseek': soulseek_client.is_configured(),
|
||||
'spotify': {
|
||||
'connected': spotify_status,
|
||||
'response_time': round(spotify_response_time, 1)
|
||||
},
|
||||
'media_server': {
|
||||
'connected': media_server_status,
|
||||
'response_time': round(media_server_response_time, 1),
|
||||
'type': active_server
|
||||
},
|
||||
'soulseek': {
|
||||
'connected': soulseek_status,
|
||||
'response_time': round(soulseek_response_time, 1)
|
||||
},
|
||||
'active_media_server': active_server
|
||||
}
|
||||
return jsonify(status_data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/system/stats')
|
||||
def get_system_stats():
|
||||
"""Get system statistics for dashboard"""
|
||||
try:
|
||||
import psutil
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
# Calculate uptime
|
||||
start_time = getattr(app, 'start_time', time.time())
|
||||
uptime_seconds = time.time() - start_time
|
||||
uptime = str(timedelta(seconds=int(uptime_seconds)))
|
||||
|
||||
# Get memory usage
|
||||
memory = psutil.virtual_memory()
|
||||
memory_usage = f"{memory.percent}%"
|
||||
|
||||
# Count active downloads from download_batches (batches that are currently downloading)
|
||||
active_downloads = len([batch_id for batch_id, batch_data in download_batches.items()
|
||||
if batch_data.get('phase') == 'downloading'])
|
||||
|
||||
# Count finished downloads (completed this session)
|
||||
finished_downloads = len([batch_id for batch_id, batch_data in download_batches.items()
|
||||
if batch_data.get('phase') == 'complete'])
|
||||
|
||||
# Calculate total download speed from active soulseek transfers
|
||||
total_download_speed = 0.0
|
||||
try:
|
||||
transfers_data = asyncio.run(soulseek_client._make_request('GET', 'transfers/downloads'))
|
||||
if transfers_data:
|
||||
for user_data in transfers_data:
|
||||
if 'directories' in user_data:
|
||||
for directory in user_data['directories']:
|
||||
if 'files' in directory:
|
||||
for file_info in directory['files']:
|
||||
state = file_info.get('state', '').lower()
|
||||
# Only count actively downloading files
|
||||
if 'inprogress' in state or 'downloading' in state or 'transferring' in state:
|
||||
speed = file_info.get('averageSpeed', 0)
|
||||
if isinstance(speed, (int, float)) and speed > 0:
|
||||
total_download_speed += float(speed)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not fetch download speeds: {e}")
|
||||
|
||||
# Convert bytes/sec to KB/s and format
|
||||
if total_download_speed > 0:
|
||||
speed_kb_s = total_download_speed / 1024
|
||||
if speed_kb_s >= 1024:
|
||||
speed_mb_s = speed_kb_s / 1024
|
||||
download_speed_str = f"{speed_mb_s:.1f} MB/s"
|
||||
else:
|
||||
download_speed_str = f"{speed_kb_s:.1f} KB/s"
|
||||
else:
|
||||
download_speed_str = "0 KB/s"
|
||||
|
||||
# Count active syncs (playlists currently syncing)
|
||||
active_syncs = 0
|
||||
# Count Spotify playlist syncs
|
||||
for playlist_id, sync_state in sync_states.items():
|
||||
if sync_state.get('status') == 'syncing':
|
||||
active_syncs += 1
|
||||
# Count YouTube playlist syncs
|
||||
for url_hash, state in youtube_playlist_states.items():
|
||||
if state.get('phase') == 'syncing':
|
||||
active_syncs += 1
|
||||
# Count Tidal playlist syncs
|
||||
for playlist_id, state in tidal_discovery_states.items():
|
||||
if state.get('phase') == 'syncing':
|
||||
active_syncs += 1
|
||||
|
||||
stats_data = {
|
||||
'active_downloads': active_downloads,
|
||||
'finished_downloads': finished_downloads,
|
||||
'download_speed': download_speed_str,
|
||||
'active_syncs': active_syncs,
|
||||
'uptime': uptime,
|
||||
'memory_usage': memory_usage
|
||||
}
|
||||
return jsonify(stats_data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/settings', methods=['GET', 'POST'])
|
||||
def handle_settings():
|
||||
global tidal_client # Declare that we might modify the global instance
|
||||
|
|
@ -10173,4 +10281,8 @@ if __name__ == '__main__':
|
|||
start_watchlist_auto_scanning()
|
||||
print("✅ Automatic watchlist scanning started")
|
||||
|
||||
# Initialize app start time for uptime tracking
|
||||
import time
|
||||
app.start_time = time.time()
|
||||
|
||||
app.run(host='0.0.0.0', port=5001, debug=True)
|
||||
|
|
|
|||
|
|
@ -6882,6 +6882,14 @@ async function loadDashboardData() {
|
|||
// Start periodic refresh of wishlist count (every 30 seconds, matching GUI behavior)
|
||||
stopWishlistCountPolling(); // Ensure no duplicates
|
||||
wishlistCountInterval = setInterval(updateWishlistCount, 30000);
|
||||
|
||||
// Initial load of service status and system statistics
|
||||
await fetchAndUpdateServiceStatus();
|
||||
await fetchAndUpdateSystemStats();
|
||||
|
||||
// Start periodic refresh of service status and system stats (every 10 seconds)
|
||||
setInterval(fetchAndUpdateServiceStatus, 10000);
|
||||
setInterval(fetchAndUpdateSystemStats, 10000);
|
||||
|
||||
// Also check the status of any ongoing update when the page loads
|
||||
await checkAndUpdateDbProgress();
|
||||
|
|
@ -12012,6 +12020,77 @@ function escapeHtml(text) {
|
|||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// --- Service Status and System Stats Functions ---
|
||||
|
||||
async function fetchAndUpdateServiceStatus() {
|
||||
try {
|
||||
const response = await fetch('/status');
|
||||
if (!response.ok) return;
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Update service status indicators and text
|
||||
updateServiceStatus('spotify', data.spotify);
|
||||
updateServiceStatus('media-server', data.media_server);
|
||||
updateServiceStatus('soulseek', data.soulseek);
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Could not fetch service status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateServiceStatus(service, statusData) {
|
||||
const indicator = document.getElementById(`${service}-status-indicator`);
|
||||
const statusText = document.getElementById(`${service}-status-text`);
|
||||
|
||||
if (indicator && statusText) {
|
||||
if (statusData.connected) {
|
||||
indicator.className = 'service-card-indicator connected';
|
||||
statusText.textContent = `Connected (${statusData.response_time}ms)`;
|
||||
statusText.className = 'service-card-status-text connected';
|
||||
} else {
|
||||
indicator.className = 'service-card-indicator disconnected';
|
||||
statusText.textContent = 'Disconnected';
|
||||
statusText.className = 'service-card-status-text disconnected';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAndUpdateSystemStats() {
|
||||
try {
|
||||
const response = await fetch('/api/system/stats');
|
||||
if (!response.ok) return;
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Update all stat cards
|
||||
updateStatCard('active-downloads-card', data.active_downloads, 'Currently downloading');
|
||||
updateStatCard('finished-downloads-card', data.finished_downloads, 'Completed this session');
|
||||
updateStatCard('download-speed-card', data.download_speed, 'Combined speed');
|
||||
updateStatCard('active-syncs-card', data.active_syncs, 'Playlists syncing');
|
||||
updateStatCard('uptime-card', data.uptime, 'Application runtime');
|
||||
updateStatCard('memory-card', data.memory_usage, 'Current usage');
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Could not fetch system stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatCard(cardId, value, subtitle) {
|
||||
const card = document.getElementById(cardId);
|
||||
if (card) {
|
||||
const valueElement = card.querySelector('.stat-card-value');
|
||||
const subtitleElement = card.querySelector('.stat-card-subtitle');
|
||||
|
||||
if (valueElement) {
|
||||
valueElement.textContent = value;
|
||||
}
|
||||
if (subtitleElement) {
|
||||
subtitleElement.textContent = subtitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Watchlist Functions ---
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue