fix(dashboard): add cache-busting and defensive chartManager guards to fix empty charts
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LLM Proxy Gateway - Admin Dashboard</title>
|
||||
<link rel="stylesheet" href="/css/dashboard.css">
|
||||
<link rel="stylesheet" href="/css/dashboard.css?v=3">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="icon" href="img/logo-icon.png" type="image/png" sizes="any">
|
||||
<link rel="apple-touch-icon" href="img/logo-icon.png">
|
||||
@@ -165,20 +165,20 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/dashboard.js"></script>
|
||||
<script src="/js/websocket.js"></script>
|
||||
<script src="/js/charts.js"></script>
|
||||
<script src="/js/pages/overview.js"></script>
|
||||
<script src="/js/pages/analytics.js"></script>
|
||||
<script src="/js/pages/costs.js"></script>
|
||||
<script src="/js/pages/clients.js"></script>
|
||||
<script src="/js/pages/providers.js"></script>
|
||||
<script src="/js/pages/models.js"></script>
|
||||
<script src="/js/pages/monitoring.js"></script>
|
||||
<script src="/js/pages/settings.js"></script>
|
||||
<script src="/js/pages/logs.js"></script>
|
||||
<!-- Scripts (cache-busted with version query params) -->
|
||||
<script src="/js/api.js?v=3"></script>
|
||||
<script src="/js/auth.js?v=3"></script>
|
||||
<script src="/js/dashboard.js?v=3"></script>
|
||||
<script src="/js/websocket.js?v=3"></script>
|
||||
<script src="/js/charts.js?v=3"></script>
|
||||
<script src="/js/pages/overview.js?v=3"></script>
|
||||
<script src="/js/pages/analytics.js?v=3"></script>
|
||||
<script src="/js/pages/costs.js?v=3"></script>
|
||||
<script src="/js/pages/clients.js?v=3"></script>
|
||||
<script src="/js/pages/providers.js?v=3"></script>
|
||||
<script src="/js/pages/models.js?v=3"></script>
|
||||
<script src="/js/pages/monitoring.js?v=3"></script>
|
||||
<script src="/js/pages/settings.js?v=3"></script>
|
||||
<script src="/js/pages/logs.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user