// Admin Dashboard JavaScript // Global variables let charts = {}; let currentTimeframe = 'minute'; let eventSource = null; // Chart.js default configuration Chart.defaults.responsive = true; Chart.defaults.maintainAspectRatio = false; // Initialize dashboard function initializeDashboard() { // Initialize all charts initializeCharts(); // Set up event handlers setupEventHandlers(); } // Initialize all charts function initializeCharts() { // Request Volume Chart const requestCtx = document.getElementById('requestChart').getContext('2d'); charts.request = new Chart(requestCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'Requests', data: [], borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.1)', tension: 0.1 }] }, options: { scales: { y: { beginAtZero: true } }, plugins: { legend: { display: false } } } }); // Language Pairs Chart const languageCtx = document.getElementById('languageChart').getContext('2d'); charts.language = new Chart(languageCtx, { type: 'doughnut', data: { labels: [], datasets: [{ data: [], backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40' ] }] }, options: { plugins: { legend: { position: 'bottom' } } } }); // Operations Chart const operationsCtx = document.getElementById('operationsChart').getContext('2d'); charts.operations = new Chart(operationsCtx, { type: 'bar', data: { labels: [], datasets: [ { label: 'Translations', data: [], backgroundColor: 'rgba(54, 162, 235, 0.8)' }, { label: 'Transcriptions', data: [], backgroundColor: 'rgba(255, 159, 64, 0.8)' } ] }, options: { scales: { y: { beginAtZero: true } } } }); // Response Time Chart const responseCtx = document.getElementById('responseTimeChart').getContext('2d'); charts.responseTime = new Chart(responseCtx, { type: 'line', data: { labels: ['Translation', 'Transcription', 'TTS'], datasets: [ { label: 'Average', data: [], borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.2)' }, { label: 'P95', data: [], borderColor: 'rgb(255, 206, 86)', backgroundColor: 'rgba(255, 206, 86, 0.2)' }, { label: 'P99', data: [], borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.2)' } ] }, options: { scales: { y: { beginAtZero: true, title: { display: true, text: 'Response Time (ms)' } } } } }); // Error Type Chart const errorCtx = document.getElementById('errorTypeChart').getContext('2d'); charts.errorType = new Chart(errorCtx, { type: 'pie', data: { labels: [], datasets: [{ data: [], backgroundColor: [ '#e74a3b', '#f6c23e', '#4e73df', '#1cc88a', '#36b9cc', '#858796' ] }] }, options: { plugins: { legend: { position: 'right' } } } }); } // Load overview statistics function loadOverviewStats() { $.ajax({ url: '/admin/api/stats/overview', method: 'GET', success: function(data) { // Update cards $('#total-requests').text(data.requests.total.toLocaleString()); $('#today-requests').text(data.requests.today.toLocaleString()); $('#active-sessions').text(data.active_sessions); $('#error-rate').text(data.error_rate + '%'); $('#cache-hit-rate').text(data.cache_hit_rate + '%'); // Update system health updateSystemHealth(data.system_health); }, error: function(xhr, status, error) { console.error('Failed to load overview stats:', error); showToast('Failed to load overview statistics', 'error'); } }); } // Update system health indicators function updateSystemHealth(health) { // Redis status const redisStatus = $('#redis-status'); redisStatus.removeClass('bg-success bg-warning bg-danger'); if (health.redis === 'healthy') { redisStatus.addClass('bg-success').text('Healthy'); } else if (health.redis === 'not_configured') { redisStatus.addClass('bg-warning').text('Not Configured'); } else { redisStatus.addClass('bg-danger').text('Unhealthy'); } // PostgreSQL status const pgStatus = $('#postgresql-status'); pgStatus.removeClass('bg-success bg-warning bg-danger'); if (health.postgresql === 'healthy') { pgStatus.addClass('bg-success').text('Healthy'); } else if (health.postgresql === 'not_configured') { pgStatus.addClass('bg-warning').text('Not Configured'); } else { pgStatus.addClass('bg-danger').text('Unhealthy'); } // ML services status (check via main app health endpoint) $.ajax({ url: '/health/detailed', method: 'GET', success: function(data) { const mlStatus = $('#ml-status'); mlStatus.removeClass('bg-success bg-warning bg-danger'); if (data.components.whisper.status === 'healthy' && data.components.tts.status === 'healthy') { mlStatus.addClass('bg-success').text('Healthy'); } else if (data.status === 'degraded') { mlStatus.addClass('bg-warning').text('Degraded'); } else { mlStatus.addClass('bg-danger').text('Unhealthy'); } } }); } // Load request chart data function loadRequestChart(timeframe) { currentTimeframe = timeframe; // Update button states $('.btn-group button').removeClass('active'); $(`button[onclick="updateRequestChart('${timeframe}')"]`).addClass('active'); $.ajax({ url: `/admin/api/stats/requests/${timeframe}`, method: 'GET', success: function(data) { charts.request.data.labels = data.labels; charts.request.data.datasets[0].data = data.data; charts.request.update(); }, error: function(xhr, status, error) { console.error('Failed to load request chart:', error); showToast('Failed to load request data', 'error'); } }); } // Update request chart function updateRequestChart(timeframe) { loadRequestChart(timeframe); } // Load operation statistics function loadOperationStats() { $.ajax({ url: '/admin/api/stats/operations', method: 'GET', success: function(data) { // Update operations chart charts.operations.data.labels = data.translations.labels; charts.operations.data.datasets[0].data = data.translations.data; charts.operations.data.datasets[1].data = data.transcriptions.data; charts.operations.update(); // Update language pairs chart const langPairs = Object.entries(data.language_pairs) .sort((a, b) => b[1] - a[1]) .slice(0, 6); // Top 6 language pairs charts.language.data.labels = langPairs.map(pair => pair[0]); charts.language.data.datasets[0].data = langPairs.map(pair => pair[1]); charts.language.update(); }, error: function(xhr, status, error) { console.error('Failed to load operation stats:', error); showToast('Failed to load operation data', 'error'); } }); } // Load error statistics function loadErrorStats() { $.ajax({ url: '/admin/api/stats/errors', method: 'GET', success: function(data) { // Update error type chart const errorTypes = Object.entries(data.error_types) .sort((a, b) => b[1] - a[1]) .slice(0, 6); charts.errorType.data.labels = errorTypes.map(type => type[0]); charts.errorType.data.datasets[0].data = errorTypes.map(type => type[1]); charts.errorType.update(); // Update recent errors list updateRecentErrors(data.recent_errors); }, error: function(xhr, status, error) { console.error('Failed to load error stats:', error); showToast('Failed to load error data', 'error'); } }); } // Update recent errors list function updateRecentErrors(errors) { const errorsList = $('#recent-errors-list'); if (errors.length === 0) { errorsList.html('
No recent errors
'); return; } let html = ''; errors.forEach(error => { const time = new Date(error.time).toLocaleString(); html += `