// Overview Page Module class OverviewPage { constructor() { this.stats = null; this.charts = {}; this.init(); } async init() { // Load data await this.loadStats(); await this.loadCharts(); await this.loadRecentRequests(); // Setup event listeners this.setupEventListeners(); // Subscribe to WebSocket updates this.setupWebSocketSubscriptions(); } async loadStats() { try { // In a real app, this would fetch from /api/usage/summary // For now, use mock data this.stats = { totalRequests: 12458, totalTokens: 1254300, totalCost: 125.43, activeClients: 8, errorRate: 2.3, avgResponseTime: 450, todayRequests: 342, todayCost: 12.45 }; 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) return; container.innerHTML = `
${this.stats.totalRequests.toLocaleString()}
Total Requests
${this.stats.todayRequests} today
${this.stats.totalTokens.toLocaleString()}
Total Tokens
12% from yesterday
$${this.stats.totalCost.toFixed(2)}
Total Cost
$${this.stats.todayCost.toFixed(2)} today
${this.stats.activeClients}
Active Clients
2 new this week
${this.stats.errorRate}%
Error Rate
0.5% improvement
${this.stats.avgResponseTime}ms
Avg Response Time
50ms faster
`; } async loadCharts() { await this.loadRequestsChart(); await this.loadProvidersChart(); await this.loadSystemHealth(); } async loadRequestsChart() { try { // Generate demo data for requests chart const data = window.chartManager.generateDemoTimeSeries(24, 1); data.datasets[0].label = 'Requests per hour'; data.datasets[0].fill = true; // Create chart this.charts.requests = window.chartManager.createLineChart('requests-chart', data, { plugins: { tooltip: { callbacks: { label: function(context) { return `Requests: ${context.parsed.y}`; } } } } }); } catch (error) { console.error('Error loading requests chart:', error); } } async loadProvidersChart() { try { const data = { labels: ['OpenAI', 'Gemini', 'DeepSeek', 'Grok'], data: [45, 25, 20, 10], colors: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6'] }; this.charts.providers = window.chartManager.createDoughnutChart('providers-chart', data, { plugins: { tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.raw || 0; return `${label}: ${value}% of requests`; } } } } }); } catch (error) { console.error('Error loading providers chart:', error); } } async loadSystemHealth() { const container = document.getElementById('system-health'); if (!container) return; const healthData = [ { label: 'API Server', status: 'online', value: 100 }, { label: 'Database', status: 'online', value: 95 }, { label: 'OpenAI', status: 'online', value: 100 }, { label: 'Gemini', status: 'online', value: 100 }, { label: 'DeepSeek', status: 'warning', value: 85 }, { label: 'Grok', status: 'offline', value: 0 } ]; container.innerHTML = healthData.map(item => `
${item.status} ${item.label}
${item.value}%
`).join(''); // Add CSS for progress bars this.addHealthStyles(); } addHealthStyles() { const style = document.createElement('style'); style.textContent = ` .health-item { margin-bottom: 1rem; } .health-label { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; } .health-name { font-size: 0.875rem; color: var(--text-primary); } .health-progress { display: flex; align-items: center; gap: 0.5rem; } .progress-bar { flex: 1; height: 6px; background-color: var(--bg-secondary); border-radius: 3px; overflow: hidden; } .progress-fill { height: 100%; border-radius: 3px; transition: width 0.3s ease; } .progress-fill.online { background-color: var(--success); } .progress-fill.warning { background-color: var(--warning); } .progress-fill.offline { background-color: var(--danger); } .health-value { font-size: 0.75rem; color: var(--text-secondary); min-width: 40px; text-align: right; } `; document.head.appendChild(style); } async loadRecentRequests() { try { // In a real app, this would fetch from /api/requests/recent // For now, use mock data const requests = [ { time: '14:32:15', client: 'client-1', provider: 'OpenAI', model: 'gpt-4', tokens: 1250, status: 'success' }, { time: '14:30:45', client: 'client-2', provider: 'Gemini', model: 'gemini-pro', tokens: 890, status: 'success' }, { time: '14:28:12', client: 'client-3', provider: 'DeepSeek', model: 'deepseek-chat', tokens: 1560, status: 'error' }, { time: '14:25:33', client: 'client-1', provider: 'OpenAI', model: 'gpt-3.5-turbo', tokens: 540, status: 'success' }, { time: '14:22:18', client: 'client-4', provider: 'Grok', model: 'grok-beta', tokens: 720, status: 'success' }, { time: '14:20:05', client: 'client-2', provider: 'Gemini', model: 'gemini-pro-vision', tokens: 1120, status: 'success' }, { time: '14:18:47', client: 'client-5', provider: 'OpenAI', model: 'gpt-4', tokens: 980, status: 'warning' }, { time: '14:15:22', client: 'client-3', provider: 'DeepSeek', model: 'deepseek-coder', tokens: 1340, status: 'success' }, { time: '14:12:10', client: 'client-1', provider: 'OpenAI', model: 'gpt-3.5-turbo', tokens: 610, status: 'success' }, { time: '14:10:05', client: 'client-6', provider: 'Gemini', model: 'gemini-pro', tokens: 830, status: 'success' } ]; this.renderRecentRequests(requests); } catch (error) { console.error('Error loading recent requests:', error); } } renderRecentRequests(requests) { const tableBody = document.querySelector('#recent-requests tbody'); if (!tableBody) return; tableBody.innerHTML = requests.map(request => { const statusClass = request.status === 'success' ? 'success' : request.status === 'error' ? 'danger' : 'warning'; const statusIcon = request.status === 'success' ? 'check-circle' : request.status === 'error' ? 'exclamation-circle' : 'exclamation-triangle'; return ` ${request.time} ${request.client} ${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', () => { // Update active state periodButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); // Update chart based on period this.updateRequestsChart(button.dataset.period); }); }); // Refresh button for recent requests const refreshBtn = document.querySelector('#recent-requests .card-action-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => { this.loadRecentRequests(); if (window.authManager) { window.authManager.showToast('Recent requests refreshed', 'success'); } }); } } setupWebSocketSubscriptions() { if (!window.wsManager) return; // Subscribe to request updates window.wsManager.subscribe('requests', (request) => { this.handleNewRequest(request); }); // Subscribe to metric updates window.wsManager.subscribe('metrics', (metric) => { this.handleNewMetric(metric); }); } handleNewRequest(request) { // Update total requests counter if (this.stats) { this.stats.totalRequests++; this.stats.todayRequests++; // Update tokens if available if (request.tokens) { this.stats.totalTokens += request.tokens; } // Re-render stats this.renderStats(); } // Add to recent requests table this.addToRecentRequests(request); } addToRecentRequests(request) { const tableBody = document.querySelector('#recent-requests tbody'); if (!tableBody) return; const time = new Date(request.timestamp || Date.now()).toLocaleTimeString(); const statusClass = request.status === 'success' ? 'success' : request.status === 'error' ? 'danger' : 'warning'; const statusIcon = request.status === 'success' ? 'check-circle' : request.status === 'error' ? 'exclamation-circle' : 'exclamation-triangle'; const row = document.createElement('tr'); row.innerHTML = ` ${time} ${request.client_id || 'Unknown'} ${request.provider || 'Unknown'} ${request.model || 'Unknown'} ${request.tokens || 0} ${request.status || 'unknown'} `; // Add to top of table tableBody.insertBefore(row, tableBody.firstChild); // Limit to 50 rows const rows = tableBody.querySelectorAll('tr'); if (rows.length > 50) { tableBody.removeChild(rows[rows.length - 1]); } } handleNewMetric(metric) { // Update charts with new metric data if (metric.type === 'requests' && this.charts.requests) { this.updateRequestsChartData(metric); } // Update system health if needed if (metric.type === 'system_health') { this.updateSystemHealth(metric); } } updateRequestsChart(period) { // In a real app, this would fetch new data based on period // For now, just update with demo data let hours = 24; if (period === '7d') hours = 24 * 7; if (period === '30d') hours = 24 * 30; const data = window.chartManager.generateDemoTimeSeries(hours, 1); data.datasets[0].label = 'Requests'; data.datasets[0].fill = true; window.chartManager.updateChartData('requests-chart', data); } updateRequestsChartData(metric) { // Add new data point to the chart if (this.charts.requests && metric.value !== undefined) { window.chartManager.addDataPoint('requests-chart', metric.value); } } updateSystemHealth(metric) { // Update system health indicators const container = document.getElementById('system-health'); if (!container || !metric.data) return; // This would update specific health indicators based on metric data // Implementation depends on metric structure } showError(message) { const container = document.getElementById('overview-stats'); if (container) { container.innerHTML = `
${message}
`; } } refresh() { this.loadStats(); this.loadRecentRequests(); // Refresh charts if (this.charts.requests) { this.charts.requests.update(); } if (this.charts.providers) { this.charts.providers.update(); } } } // Initialize overview page when needed window.initOverview = async () => { window.overviewPage = new OverviewPage(); }; // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = OverviewPage; }