// Overview Page Module class OverviewPage { constructor() { this.stats = null; this.charts = {}; this.init(); } async init() { // Load data await Promise.all([ this.loadStats(), this.loadCharts(), this.loadRecentRequests() ]); // Setup event listeners this.setupEventListeners(); // Subscribe to WebSocket updates this.setupWebSocketSubscriptions(); } async loadStats() { try { const data = await window.api.get('/usage/summary'); this.stats = data; this.renderStats(); } catch (error) { console.error('Error loading stats:', error); this.showError('Failed to load statistics'); } } renderStats() { const container = document.getElementById('overview-stats'); if (!container || !this.stats) return; container.innerHTML = `
${this.stats.total_requests.toLocaleString()}
Total Requests
${this.stats.today_requests > 0 ? ` ${this.stats.today_requests} today` : 'No requests today'}
${window.api.formatNumber(this.stats.total_tokens)}
Total Tokens
Lifetime usage
${window.api.formatCurrency(this.stats.total_cost)}
Total Cost
${this.stats.today_cost > 0 ? ` ${window.api.formatCurrency(this.stats.today_cost)} today` : '$0.00 today'}
${this.stats.active_clients}
Active Clients
Unique callers
${this.stats.error_rate.toFixed(1)}%
Error Rate
${this.stats.error_rate > 5 ? 'Action required' : 'System healthy'}
${Math.round(this.stats.avg_response_time)}ms
Avg Latency
Across all providers
`; } async loadCharts() { await Promise.all([ this.loadRequestsChart(), this.loadProvidersChart(), this.loadSystemHealth() ]); } async loadRequestsChart() { try { const data = await window.api.get('/usage/time-series'); const series = data.series || []; if (series.length === 0) { this.showEmptyChart('requests-chart', 'No request data in the last 24 hours'); return; } const chartData = { labels: series.map(item => item.time), datasets: [ { label: 'Requests', data: series.map(item => item.requests), color: '#3b82f6', fill: true } ] }; this.charts.requests = window.chartManager.createLineChart('requests-chart', chartData); } catch (error) { console.error('Error loading requests chart:', error); this.showEmptyChart('requests-chart', 'Failed to load request data'); } } async loadProvidersChart() { try { const data = await window.api.get('/usage/providers'); if (!data || data.length === 0) { this.showEmptyChart('providers-chart', 'No provider usage data yet'); return; } 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]) }; this.charts.providers = window.chartManager.createDoughnutChart('providers-chart', chartData); } catch (error) { console.error('Error loading providers chart:', error); this.showEmptyChart('providers-chart', 'Failed to load provider data'); } } showEmptyChart(canvasId, message) { const canvas = document.getElementById(canvasId); if (!canvas) return; const container = canvas.closest('.chart-container'); if (container) { canvas.style.display = 'none'; let msg = container.querySelector('.empty-chart-msg'); if (!msg) { msg = document.createElement('div'); msg.className = 'empty-chart-msg'; msg.style.cssText = 'display:flex;align-items:center;justify-content:center;height:200px;color:var(--fg4);font-size:0.9rem;'; container.appendChild(msg); } msg.textContent = message; } } async loadSystemHealth() { const container = document.getElementById('system-health'); if (!container) return; try { const data = await window.api.get('/system/health'); const components = data.components; container.innerHTML = Object.entries(components).map(([name, status]) => `
${status} ${name.toUpperCase()}
`).join(''); } catch (error) { console.error('Error loading health:', error); } } async loadRecentRequests() { try { const requests = await window.api.get('/system/logs'); this.renderRecentRequests(requests.slice(0, 10)); // Just show top 10 on overview } catch (error) { console.error('Error loading recent requests:', error); } } renderRecentRequests(requests) { const tableBody = document.querySelector('#recent-requests tbody'); if (!tableBody) return; if (requests.length === 0) { tableBody.innerHTML = 'No recent requests'; return; } tableBody.innerHTML = requests.map(request => { const statusClass = request.status === 'success' ? 'success' : 'danger'; const statusIcon = request.status === 'success' ? 'check-circle' : 'exclamation-circle'; const time = luxon.DateTime.fromISO(request.timestamp).toFormat('HH:mm:ss'); return ` ${time} ${request.client_id} ${request.provider} ${request.model} ${request.tokens.toLocaleString()} ${request.status} `; }).join(''); } setupEventListeners() { // Period buttons for requests chart const periodButtons = document.querySelectorAll('.chart-control-btn[data-period]'); periodButtons.forEach(button => { button.addEventListener('click', () => { periodButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); this.loadRequestsChart(); // In real app, pass period to API }); }); const refreshBtn = document.querySelector('#recent-requests .card-action-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => { this.loadRecentRequests(); window.authManager.showToast('Recent requests refreshed', 'success'); }); } } setupWebSocketSubscriptions() { if (!window.wsManager) return; window.wsManager.subscribe('requests', (event) => { // Hot-reload stats and table when a new request comes in this.loadStats(); this.addToRecentRequests(event); }); } addToRecentRequests(request) { const tableBody = document.querySelector('#recent-requests tbody'); if (!tableBody) return; // Remove empty message if present if (tableBody.querySelector('.text-center')) { tableBody.innerHTML = ''; } const time = luxon.DateTime.fromISO(request.timestamp || new Date().toISOString()).toFormat('HH:mm:ss'); const statusClass = request.status === 'success' ? 'success' : 'danger'; const statusIcon = request.status === 'success' ? 'check-circle' : 'exclamation-circle'; const row = document.createElement('tr'); row.innerHTML = ` ${time} ${request.client_id} ${request.provider} ${request.model} ${(request.total_tokens || request.tokens || 0).toLocaleString()} ${request.status} `; tableBody.insertBefore(row, tableBody.firstChild); if (tableBody.children.length > 10) { tableBody.lastElementChild.remove(); } } showError(message) { const container = document.getElementById('overview-stats'); if (container) { container.innerHTML = `
${message}
`; } } } window.initOverview = async () => { window.overviewPage = new OverviewPage(); };