diff --git a/static/index.html b/static/index.html index 44eeb247..bedb76f2 100644 --- a/static/index.html +++ b/static/index.html @@ -4,7 +4,7 @@ LLM Proxy Gateway - Admin Dashboard - + @@ -165,20 +165,20 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/js/api.js b/static/js/api.js index 0414e973..ad7a6b1a 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -88,3 +88,15 @@ class ApiClient { } window.api = new ApiClient(); + +// Helper: waits until chartManager is available (defensive against load-order edge cases) +window.waitForChartManager = async (timeoutMs = 3000) => { + if (window.chartManager) return window.chartManager; + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + await new Promise(r => setTimeout(r, 50)); + if (window.chartManager) return window.chartManager; + } + console.warn('chartManager not available after', timeoutMs, 'ms'); + return null; +}; diff --git a/static/js/dashboard.js b/static/js/dashboard.js index f1d28618..aef38875 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -79,8 +79,9 @@ class Dashboard { } async loadPage(page) { - if (window.chartManager) { - window.chartManager.destroyAllCharts(); + const cm = window.chartManager; + if (cm) { + cm.destroyAllCharts(); } this.currentPage = page; diff --git a/static/js/pages/analytics.js b/static/js/pages/analytics.js index 5fb2c413..a6a6c6de 100644 --- a/static/js/pages/analytics.js +++ b/static/js/pages/analytics.js @@ -50,6 +50,14 @@ class AnalyticsPage { } async loadCharts() { + const cm = window.chartManager || await window.waitForChartManager(); + if (!cm) { + this.showEmptyChart('analytics-chart', 'Chart system unavailable'); + this.showEmptyChart('clients-chart', 'Chart system unavailable'); + this.showEmptyChart('models-chart', 'Chart system unavailable'); + return; + } + // Fetch each data source independently so one failure doesn't kill the others const [timeSeriesResult, breakdownResult] = await Promise.allSettled([ window.api.get('/usage/time-series'), @@ -111,6 +119,8 @@ class AnalyticsPage { } renderAnalyticsChart(series) { + const cm = window.chartManager; + if (!cm) return; const data = { labels: series.map(s => s.time), datasets: [ @@ -130,10 +140,12 @@ class AnalyticsPage { ] }; - window.chartManager.createLineChart('analytics-chart', data); + cm.createLineChart('analytics-chart', data); } renderClientsChart(clients) { + const cm = window.chartManager; + if (!cm) return; const data = { labels: clients.map(c => c.label), datasets: [{ @@ -143,17 +155,19 @@ class AnalyticsPage { }] }; - window.chartManager.createHorizontalBarChart('clients-chart', data); + cm.createHorizontalBarChart('clients-chart', data); } renderModelsChart(models) { + const cm = window.chartManager; + if (!cm) return; const data = { labels: models.map(m => m.label), data: models.map(m => m.value), - colors: window.chartManager.defaultColors + colors: cm.defaultColors }; - window.chartManager.createDoughnutChart('models-chart', data); + cm.createDoughnutChart('models-chart', data); } async loadUsageData() { diff --git a/static/js/pages/clients.js b/static/js/pages/clients.js index 6e72241d..51552052 100644 --- a/static/js/pages/clients.js +++ b/static/js/pages/clients.js @@ -75,6 +75,9 @@ class ClientsPage { async loadClientUsageChart() { try { + const cm = window.chartManager || await window.waitForChartManager(); + if (!cm) { this.showEmptyChart('client-usage-chart', 'Chart system unavailable'); return; } + const data = await window.api.get('/usage/clients'); if (!data || data.length === 0) { @@ -91,7 +94,7 @@ class ClientsPage { }] }; - window.chartManager.createHorizontalBarChart('client-usage-chart', chartData); + cm.createHorizontalBarChart('client-usage-chart', chartData); } catch (error) { console.error('Error loading client usage chart:', error); diff --git a/static/js/pages/costs.js b/static/js/pages/costs.js index 60bee014..d84a1257 100644 --- a/static/js/pages/costs.js +++ b/static/js/pages/costs.js @@ -91,6 +91,9 @@ class CostsPage { async loadCostsChart() { try { + const cm = window.chartManager || await window.waitForChartManager(); + if (!cm) { this.showEmptyChart('costs-chart', 'Chart system unavailable'); return; } + const data = await window.api.get('/usage/providers'); if (!data || data.length === 0) { @@ -107,7 +110,7 @@ class CostsPage { }] }; - window.chartManager.createBarChart('costs-chart', chartData); + cm.createBarChart('costs-chart', chartData); } catch (error) { console.error('Error loading costs chart:', error); diff --git a/static/js/pages/monitoring.js b/static/js/pages/monitoring.js index f2af8426..e6f44167 100644 --- a/static/js/pages/monitoring.js +++ b/static/js/pages/monitoring.js @@ -153,6 +153,13 @@ class MonitoringPage { } async loadCharts() { + // Ensure chartManager is available + const cm = window.chartManager || await window.waitForChartManager(); + if (!cm) { + console.warn('chartManager unavailable, skipping monitoring charts'); + return; + } + // Fetch recent logs for chart data try { const logs = await window.api.get('/system/logs'); @@ -168,6 +175,8 @@ class MonitoringPage { async loadResponseTimeChart() { try { + const cm = window.chartManager; + if (!cm) return; // Bucket recent logs by minute for latency chart const buckets = this.bucketByMinute(this.recentLogs, 20); const labels = buckets.map(b => b.label); @@ -187,7 +196,7 @@ class MonitoringPage { }] }; - window.chartManager.createLineChart('response-time-chart', data, { + cm.createLineChart('response-time-chart', data, { scales: { y: { title: { display: true, text: 'Milliseconds' }, @@ -202,6 +211,8 @@ class MonitoringPage { async loadErrorRateChart() { try { + const cm = window.chartManager; + if (!cm) return; const buckets = this.bucketByMinute(this.recentLogs, 20); const labels = buckets.map(b => b.label); const values = buckets.map(b => { @@ -220,7 +231,7 @@ class MonitoringPage { }] }; - window.chartManager.createLineChart('error-rate-chart', data, { + cm.createLineChart('error-rate-chart', data, { scales: { y: { title: { display: true, text: 'Percentage' }, @@ -238,6 +249,8 @@ class MonitoringPage { async loadRateLimitChart() { try { + const cm = window.chartManager; + if (!cm) return; // Show requests-per-client from recent logs const clientCounts = {}; for (const log of this.recentLogs) { @@ -257,7 +270,7 @@ class MonitoringPage { }] }; - window.chartManager.createBarChart('rate-limit-chart', data, { + cm.createBarChart('rate-limit-chart', data, { scales: { y: { title: { display: true, text: 'Request Count' }, @@ -452,22 +465,24 @@ class MonitoringPage { } updateCharts(metric) { + const cm = window.chartManager; + if (!cm) return; // Update charts with new metric data - if (metric.type === 'response_time' && window.chartManager.charts.has('response-time-chart')) { + if (metric.type === 'response_time' && cm.charts.has('response-time-chart')) { this.updateResponseTimeChart(metric.value); } - if (metric.type === 'error_rate' && window.chartManager.charts.has('error-rate-chart')) { + if (metric.type === 'error_rate' && cm.charts.has('error-rate-chart')) { this.updateErrorRateChart(metric.value); } } updateResponseTimeChart(value) { - window.chartManager.addDataPoint('response-time-chart', value); + if (window.chartManager) window.chartManager.addDataPoint('response-time-chart', value); } updateErrorRateChart(value) { - window.chartManager.addDataPoint('error-rate-chart', value); + if (window.chartManager) window.chartManager.addDataPoint('error-rate-chart', value); } startDemoUpdates() { diff --git a/static/js/pages/overview.js b/static/js/pages/overview.js index 095a8ea1..d992570f 100644 --- a/static/js/pages/overview.js +++ b/static/js/pages/overview.js @@ -128,6 +128,9 @@ class OverviewPage { async loadRequestsChart() { try { + const cm = window.chartManager || await window.waitForChartManager(); + if (!cm) { this.showEmptyChart('requests-chart', 'Chart system unavailable'); return; } + const data = await window.api.get('/usage/time-series'); const series = data.series || []; @@ -148,7 +151,7 @@ class OverviewPage { ] }; - this.charts.requests = window.chartManager.createLineChart('requests-chart', chartData); + this.charts.requests = cm.createLineChart('requests-chart', chartData); } catch (error) { console.error('Error loading requests chart:', error); this.showEmptyChart('requests-chart', 'Failed to load request data'); @@ -157,6 +160,9 @@ class OverviewPage { async loadProvidersChart() { try { + const cm = window.chartManager || await window.waitForChartManager(); + if (!cm) { this.showEmptyChart('providers-chart', 'Chart system unavailable'); return; } + const data = await window.api.get('/usage/providers'); if (!data || data.length === 0) { @@ -167,10 +173,10 @@ class OverviewPage { const chartData = { labels: data.map(item => item.provider), data: data.map(item => item.requests), - colors: data.map((_, i) => window.chartManager.defaultColors[i % window.chartManager.defaultColors.length]) + colors: data.map((_, i) => cm.defaultColors[i % cm.defaultColors.length]) }; - this.charts.providers = window.chartManager.createDoughnutChart('providers-chart', chartData); + this.charts.providers = cm.createDoughnutChart('providers-chart', chartData); } catch (error) { console.error('Error loading providers chart:', error); this.showEmptyChart('providers-chart', 'Failed to load provider data');