fix(dashboard): fix chart crash, field name mismatches, and demo data injection
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

- overview.js: fix time-series chart crash (data is {series:[...]}, not array; field is 'time' not 'hour')
- monitoring.js: use fallback field names (total_tokens/tokens, duration_ms/duration) for WebSocket vs API compat
- monitoring.js: disable localhost demo data injection that mixed fake data with real
- websocket.js: fix duplicate condition and field name mismatches in dead-code handlers
- logging/mod.rs: add info! logs for successful DB insert and broadcast count for diagnostics
This commit is contained in:
2026-03-02 10:14:20 -05:00
parent 9318336f62
commit d5d869dcc6
4 changed files with 20 additions and 35 deletions

View File

@@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
use serde::Serialize; use serde::Serialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tracing::warn; use tracing::{info, warn};
use crate::errors::AppError; use crate::errors::AppError;
@@ -42,14 +42,19 @@ impl RequestLogger {
// Spawn async task to log without blocking response // Spawn async task to log without blocking response
tokio::spawn(async move { tokio::spawn(async move {
// Broadcast to dashboard // Broadcast to dashboard
let _ = tx.send(serde_json::json!({ let broadcast_result = tx.send(serde_json::json!({
"type": "request", "type": "request",
"channel": "requests", "channel": "requests",
"payload": log "payload": log
})); }));
match broadcast_result {
Ok(receivers) => info!("Broadcast request log to {} dashboard listeners", receivers),
Err(_) => {} // No active WebSocket clients — expected when dashboard isn't open
}
if let Err(e) = Self::insert_log(&pool, log).await { match Self::insert_log(&pool, log).await {
warn!("Failed to log request to database: {}", e); Ok(()) => info!("Request logged to database successfully"),
Err(e) => warn!("Failed to log request to database: {}", e),
} }
}); });
} }

View File

@@ -403,7 +403,7 @@ class MonitoringPage {
<strong>${request.client_id || 'Unknown'}</strong> → <strong>${request.client_id || 'Unknown'}</strong> →
${request.provider || 'Unknown'} (${request.model || 'Unknown'}) ${request.provider || 'Unknown'} (${request.model || 'Unknown'})
<div class="stream-entry-details"> <div class="stream-entry-details">
${request.tokens || 0} tokens • ${request.duration || 0}ms ${request.total_tokens || request.tokens || 0} tokens • ${request.duration_ms || request.duration || 0}ms
</div> </div>
</div> </div>
`; `;
@@ -493,28 +493,7 @@ class MonitoringPage {
} }
startDemoUpdates() { startDemoUpdates() {
// Simulate incoming requests for demo purposes // Demo updates disabled — real data comes via WebSocket subscriptions
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
setInterval(() => {
if (!this.isPaused && Math.random() > 0.3) { // 70% chance
this.simulateRequest();
}
}, 2000);
// Simulate logs
setInterval(() => {
if (!this.isPaused && Math.random() > 0.5) { // 50% chance
this.simulateLog();
}
}, 3000);
// Simulate metrics
setInterval(() => {
if (!this.isPaused) {
this.simulateMetric();
}
}, 5000);
}
} }
simulateRequest() { simulateRequest() {

View File

@@ -129,13 +129,14 @@ class OverviewPage {
async loadRequestsChart() { async loadRequestsChart() {
try { try {
const data = await window.api.get('/usage/time-series'); const data = await window.api.get('/usage/time-series');
const series = data.series || [];
const chartData = { const chartData = {
labels: data.map(item => item.hour), labels: series.map(item => item.time),
datasets: [ datasets: [
{ {
label: 'Requests', label: 'Requests',
data: data.map(item => item.requests), data: series.map(item => item.requests),
color: '#3b82f6', color: '#3b82f6',
fill: true fill: true
} }

View File

@@ -88,8 +88,8 @@ class WebSocketManager {
} }
handleMessage(data) { handleMessage(data) {
// Handle global events // Handle request events
if (data.type === 'request' || data.type === 'request') { if (data.type === 'request') {
this.notify('requests', data.payload); this.notify('requests', data.payload);
} }
@@ -286,9 +286,9 @@ class WebSocketManager {
// Update token counters // Update token counters
const tokenCountElement = document.querySelector('[data-stat="total-tokens"]'); const tokenCountElement = document.querySelector('[data-stat="total-tokens"]');
if (tokenCountElement && request.tokens) { if (tokenCountElement && (request.total_tokens || request.tokens)) {
const currentTokens = parseInt(tokenCountElement.textContent) || 0; const currentTokens = parseInt(tokenCountElement.textContent) || 0;
tokenCountElement.textContent = currentTokens + request.tokens; tokenCountElement.textContent = currentTokens + (request.total_tokens || request.tokens);
} }
} }
@@ -312,7 +312,7 @@ class WebSocketManager {
<td>${request.client_id || 'Unknown'}</td> <td>${request.client_id || 'Unknown'}</td>
<td>${request.provider || 'Unknown'}</td> <td>${request.provider || 'Unknown'}</td>
<td>${request.model || 'Unknown'}</td> <td>${request.model || 'Unknown'}</td>
<td>${request.tokens || 0}</td> <td>${(request.total_tokens || request.tokens || 0)}</td>
<td> <td>
<span class="status-badge ${statusClass}"> <span class="status-badge ${statusClass}">
<i class="fas fa-${statusIcon}"></i> <i class="fas fa-${statusIcon}"></i>
@@ -362,7 +362,7 @@ class WebSocketManager {
<strong>${request.client_id || 'Unknown'}</strong> → <strong>${request.client_id || 'Unknown'}</strong> →
${request.provider || 'Unknown'} (${request.model || 'Unknown'}) ${request.provider || 'Unknown'} (${request.model || 'Unknown'})
<div class="stream-entry-details"> <div class="stream-entry-details">
${request.tokens || 0} tokens • ${request.duration || 0}ms ${(request.total_tokens || request.tokens || 0)} tokens • ${(request.duration_ms || request.duration || 0)}ms
</div> </div>
</div> </div>
`; `;