diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index f2e2716..3946dc9 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -539,6 +539,7 @@
Link Playlists
Active Playlists
Configuration
+
API Analytics
@@ -980,6 +981,85 @@ + + +
+
+

+ API Endpoint Usage +
+ + +
+

+

+ Track which Jellyfin API endpoints are being called most frequently. Useful for debugging and understanding client behavior. +

+ +
+
+
Total Requests
+
0
+
+
+
Unique Endpoints
+
0
+
+
+
Most Called
+
-
+
+
+ +
+ + +
+ +
+ + + + + + + + + + + + + + +
#EndpointRequests% of Total
+ Loading endpoint usage data... +
+
+
+ +
+

About Endpoint Tracking

+

+ Allstarr logs every Jellyfin API endpoint call to help you understand how clients interact with your server. + This data is stored in /app/cache/endpoint-usage/endpoints.csv + and persists across restarts. +

+ Common Endpoints: +

+

+
+
@@ -2864,6 +2944,7 @@ fetchJellyfinUsers(); fetchJellyfinPlaylists(); fetchConfig(); + fetchEndpointUsage(); // Auto-refresh every 30 seconds setInterval(() => { @@ -2872,7 +2953,102 @@ fetchTrackMappings(); fetchMissingTracks(); fetchDownloads(); + + // Refresh endpoint usage if on that tab + const endpointsTab = document.getElementById('tab-endpoints'); + if (endpointsTab && endpointsTab.classList.contains('active')) { + fetchEndpointUsage(); + } }, 30000); + + // Endpoint Usage Functions + async function fetchEndpointUsage() { + try { + const topSelect = document.getElementById('endpoints-top-select'); + const top = topSelect ? topSelect.value : 50; + + const res = await fetch(`/api/admin/debug/endpoint-usage?top=${top}`); + const data = await res.json(); + + // Update summary stats + document.getElementById('endpoints-total-requests').textContent = data.totalRequests?.toLocaleString() || '0'; + document.getElementById('endpoints-unique-count').textContent = data.totalEndpoints?.toLocaleString() || '0'; + + const mostCalled = data.endpoints && data.endpoints.length > 0 + ? data.endpoints[0].endpoint + : '-'; + document.getElementById('endpoints-most-called').textContent = mostCalled; + + // Update table + const tbody = document.getElementById('endpoints-table-body'); + + if (!data.endpoints || data.endpoints.length === 0) { + tbody.innerHTML = 'No endpoint usage data available yet. Data will appear as clients make requests.'; + return; + } + + tbody.innerHTML = data.endpoints.map((ep, index) => { + const percentage = data.totalRequests > 0 + ? ((ep.count / data.totalRequests) * 100).toFixed(1) + : '0.0'; + + // Color code based on usage + let countColor = 'var(--text-primary)'; + if (ep.count > 1000) countColor = 'var(--error)'; + else if (ep.count > 100) countColor = 'var(--warning)'; + else if (ep.count > 10) countColor = 'var(--accent)'; + + // Highlight common patterns + let endpointDisplay = ep.endpoint; + if (ep.endpoint.includes('/stream')) { + endpointDisplay = `${escapeHtml(ep.endpoint)}`; + } else if (ep.endpoint.includes('/Playing')) { + endpointDisplay = `${escapeHtml(ep.endpoint)}`; + } else if (ep.endpoint.includes('/Search')) { + endpointDisplay = `${escapeHtml(ep.endpoint)}`; + } else { + endpointDisplay = escapeHtml(ep.endpoint); + } + + return ` + + ${index + 1} + ${endpointDisplay} + ${ep.count.toLocaleString()} + ${percentage}% + + `; + }).join(''); + + } catch (error) { + console.error('Failed to fetch endpoint usage:', error); + const tbody = document.getElementById('endpoints-table-body'); + tbody.innerHTML = 'Failed to load endpoint usage data'; + } + } + + async function clearEndpointUsage() { + if (!confirm('Are you sure you want to clear all endpoint usage data? This cannot be undone.')) { + return; + } + + try { + const res = await fetch('/api/admin/debug/endpoint-usage', { method: 'DELETE' }); + const data = await res.json(); + + showToast(data.message || 'Endpoint usage data cleared', 'success'); + fetchEndpointUsage(); + } catch (error) { + console.error('Failed to clear endpoint usage:', error); + showToast('Failed to clear endpoint usage data', 'error'); + } + } + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + }