From 0c5bfcae1f76efb473d5319b74ef1a76fa4f7ec3 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:53:09 -0700 Subject: [PATCH] Add API rate and Spotify rate limit info to Copy Debug Info - Backend: include api_rates (per-service calls/min + Spotify endpoints) and spotify_rate_limit (active, remaining, trigger endpoint) in debug-info - Frontend: format API rates table with service name, cpm, limit, percentage, and Spotify endpoint breakdown. Show bold warning block when rate limited with trigger endpoint, remaining time, and retry-after value --- web_server.py | 22 ++++++++++++++++++++++ webui/static/docs.js | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/web_server.py b/web_server.py index 49997518..291d96fb 100644 --- a/web_server.py +++ b/web_server.py @@ -4523,6 +4523,28 @@ def get_debug_info(): 'm3u_export_enabled': config_manager.get('m3u.enabled', False), } + # API rate monitor — current calls/min per service + Spotify rate limit state + try: + from core.api_call_tracker import api_call_tracker + rates = api_call_tracker.get_all_rates() + info['api_rates'] = rates + # Spotify rate limit details + if spotify_client: + rl_info = spotify_client.get_rate_limit_info() + if rl_info: + info['spotify_rate_limit'] = { + 'active': True, + 'remaining_seconds': rl_info.get('remaining_seconds', 0), + 'retry_after': rl_info.get('retry_after', 0), + 'endpoint': rl_info.get('endpoint', ''), + 'expires_at': rl_info.get('expires_at', ''), + } + else: + info['spotify_rate_limit'] = {'active': False} + except Exception: + info['api_rates'] = {} + info['spotify_rate_limit'] = {'active': False} + # Database size db_path = os.path.join('database', 'music_library.db') if os.path.exists(db_path): diff --git a/webui/static/docs.js b/webui/static/docs.js index e87e4f42..ab8fa626 100644 --- a/webui/static/docs.js +++ b/webui/static/docs.js @@ -2291,6 +2291,29 @@ function initializeDocsPage() { } text += '\n'; + text += '── API Rates (calls/min) ──\n'; + if (data.api_rates) { + Object.entries(data.api_rates).forEach(([svc, info]) => { + const cpm = info.cpm || 0; + const limit = info.limit || '?'; + const pct = limit ? Math.round(cpm / limit * 100) : 0; + text += `${svc.padEnd(14)} ${String(cpm).padStart(5)}/min (limit: ${limit}, ${pct}%)`; + if (info.endpoints && Object.keys(info.endpoints).length > 0) { + text += ` endpoints: ${Object.entries(info.endpoints).map(([e, c]) => `${e}:${c}`).join(', ')}`; + } + text += '\n'; + }); + } + if (data.spotify_rate_limit?.active) { + const rl = data.spotify_rate_limit; + const mins = Math.ceil((rl.remaining_seconds || 0) / 60); + text += `\n*** SPOTIFY RATE LIMITED ***\n`; + text += `Triggered by: ${rl.endpoint || 'unknown'}\n`; + text += `Remaining: ${mins} minutes\n`; + text += `Retry-After: ${rl.retry_after || '?'}s\n`; + } + text += '\n'; + text += `── Logs: ${data.log_source || 'app'}.log (last ${data.recent_logs?.length || 0} lines) ──\n`; if (data.recent_logs?.length) { data.recent_logs.forEach(line => { text += line + '\n'; });