{% block content %}{% endblock %}
{% block scripts %}{% endblock %} `).join('')} `; modal.classList.remove('hidden'); } function closeUserModal() { document.getElementById('userDetailModal').classList.add('hidden'); } async function deleteUser(userId, email) { if (!confirm(`Are you sure you want to delete user "${email}" and all their data? This action cannot be undone.`)) { return; } try { const response = await fetch(`/admin/users/${userId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showToast('User deleted successfully', 'success'); loadUsers(); // Refresh the table } else { showToast('Failed to delete user', 'error'); } } catch (error) { console.error('Delete user error:', error); showToast('Failed to delete user', 'error'); } } async function loadDatabaseSchema() { try { const response = await fetch('/admin/database/schema'); const data = await response.json(); if (data.success) { updateDatabaseSchema(data.schema); } } catch (error) { console.error('Schema loading error:', error); showToast('Failed to load database schema', 'error'); } } function updateDatabaseSchema(schema) { const container = document.getElementById('databaseSchema'); container.innerHTML = Object.entries(schema).map(([tableName, tableInfo]) => `
${tableName}
${tableInfo.row_count} rows
${tableInfo.columns.map(col => `
${col.name} ${col.type} ${col.primary_key ? 'PK' : ''} ${col.not_null ? 'NOT NULL' : ''}
`).join('')}
`).join(''); } async function loadTokenUsage() { try { // For now, create sample charts with existing data await createTokenUsageCharts(); } catch (error) { console.error('Token usage loading error:', error); showToast('Failed to load token usage data', 'error'); } } async function createTokenUsageCharts() { // Provider Usage Chart const providerCtx = document.getElementById('providerUsageChart').getContext('2d'); new Chart(providerCtx, { type: 'bar', data: { labels: ['Groq', 'Gemini'], datasets: [{ label: 'Input Tokens', data: [150000, 85000], backgroundColor: 'rgba(59, 130, 246, 0.8)', borderColor: 'rgba(59, 130, 246, 1)', borderWidth: 1 }, { label: 'Output Tokens', data: [45000, 28000], backgroundColor: 'rgba(147, 51, 234, 0.8)', borderColor: 'rgba(147, 51, 234, 1)', borderWidth: 1 }] }, options: { responsive: true, plugins: { title: { display: true, text: 'Token Usage by Provider' } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Tokens' } } } } }); // Cost Breakdown Chart const costCtx = document.getElementById('costBreakdownChart').getContext('2d'); new Chart(costCtx, { type: 'pie', data: { labels: ['Conversation', 'Content Analysis', 'Recommendations', 'Study Planning'], datasets: [{ data: [40, 25, 20, 15], backgroundColor: [ 'rgba(34, 197, 94, 0.8)', 'rgba(59, 130, 246, 0.8)', 'rgba(147, 51, 234, 0.8)', 'rgba(251, 146, 60, 0.8)' ], borderColor: [ 'rgba(34, 197, 94, 1)', 'rgba(59, 130, 246, 1)', 'rgba(147, 51, 234, 1)', 'rgba(251, 146, 60, 1)' ], borderWidth: 2 }] }, options: { responsive: true, plugins: { title: { display: true, text: 'Cost Distribution by Operation Type' }, legend: { position: 'bottom' } } } }); } function refreshUsers() { currentPage = 1; loadUsers(); } function previousPage() { if (currentPage > 1) { currentPage--; loadUsers(); } } function nextPage() { if (usersData && currentPage < usersData.total_pages) { currentPage++; loadUsers(); } } function createCharts(stats) { createUserGrowthChart(stats); createTokenCostChart(stats); } function createUserGrowthChart(stats) { const ctx = document.getElementById('userGrowthChart').getContext('2d'); // Generate sample data for last 30 days const dates = []; const userData = []; const today = new Date(); for (let i = 29; i >= 0; i--) { const date = new Date(today); date.setDate(date.getDate() - i); dates.push(date.toLocaleDateString()); // Simulate growth data (would come from real analytics) userData.push(Math.max(0, stats.users?.total - Math.floor(Math.random() * i * 2))); } new Chart(ctx, { type: 'line', data: { labels: dates, datasets: [{ label: 'Total Users', data: userData, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, plugins: { title: { display: true, text: 'User Growth Over Time' } }, interaction: { intersect: false, }, scales: { x: { display: true, title: { display: true, text: 'Date' } }, y: { display: true, title: { display: true, text: 'Users' } } } } }); } function createTokenCostChart(stats) { const ctx = document.getElementById('tokenCostChart').getContext('2d'); // Sample cost data const providers = ['Groq', 'Gemini']; const costs = [ (stats.api_usage?.estimated_cost || 0) * 0.6, // Groq portion (stats.api_usage?.estimated_cost || 0) * 0.4 // Gemini portion ]; new Chart(ctx, { type: 'doughnut', data: { labels: providers, datasets: [{ label: 'Cost ($)', data: costs, backgroundColor: [ 'rgba(59, 130, 246, 0.8)', 'rgba(147, 51, 234, 0.8)' ], borderColor: [ 'rgba(59, 130, 246, 1)', 'rgba(147, 51, 234, 1)' ], borderWidth: 2 }] }, options: { responsive: true, plugins: { title: { display: true, text: 'API Costs by Provider' }, legend: { position: 'bottom', } } } }); } function exportUsers() { window.open('/admin/export/users', '_blank'); showToast('Users data export started', 'success'); } function exportTokens() { window.open('/admin/export/tokens', '_blank'); showToast('Token usage data export started', 'success'); } async function loadSystemHealth() { try { // Load system health metrics const healthResponse = await fetch('/admin/system/health'); const healthData = await healthResponse.json(); if (healthData.success) { updateSystemMetrics(healthData.health); } // Load system alerts const alertsResponse = await fetch('/admin/system/alerts'); const alertsData = await alertsResponse.json(); if (alertsData.success) { updateSystemAlerts(alertsData.alerts); } } catch (error) { console.error('System health loading error:', error); showToast('Failed to load system health data', 'error'); } } function updateSystemMetrics(health) { // Memory metrics if (health.memory) { const memoryPercent = health.memory.percent || 0; const memoryUsed = Math.round(health.memory.used / 1024 / 1024 / 1024 * 100) / 100; const memoryTotal = Math.round(health.memory.total / 1024 / 1024 / 1024 * 100) / 100; document.getElementById('memoryMetrics').innerHTML = `
Used: ${memoryUsed}GB / ${memoryTotal}GB
${memoryPercent.toFixed(1)}% used
`; } // Disk metrics if (health.disk) { const diskPercent = health.disk.percent || 0; const diskUsed = Math.round(health.disk.used / 1024 / 1024 / 1024 * 100) / 100; const diskTotal = Math.round(health.disk.total / 1024 / 1024 / 1024 * 100) / 100; document.getElementById('diskMetrics').innerHTML = `
Used: ${diskUsed}GB / ${diskTotal}GB
${diskPercent.toFixed(1)}% used
`; } // Database metrics if (health.database) { document.getElementById('databaseMetrics').innerHTML = `
Size: ${health.database.size_mb}MB
Uptime: ${health.uptime || 'Unknown'}
`; } // Error logs if (health.recent_errors && health.recent_errors.length > 0) { document.getElementById('errorLogs').innerHTML = `
${health.recent_errors.map(error => `
${error.level} ${error.timestamp}
${error.message}
Module: ${error.module}
`).join('')}
`; } else { document.getElementById('errorLogs').innerHTML = '
No recent errors
'; } } function updateSystemAlerts(alerts) { const container = document.getElementById('systemAlerts'); if (alerts.length === 0) { container.innerHTML = '
✅ All systems normal
'; return; } container.innerHTML = alerts.map(alert => { const bgColor = alert.type === 'error' ? 'bg-red-50 border-red-200' : alert.type === 'warning' ? 'bg-yellow-50 border-yellow-200' : 'bg-blue-50 border-blue-200'; const textColor = alert.type === 'error' ? 'text-red-800' : alert.type === 'warning' ? 'text-yellow-800' : 'text-blue-800'; const icon = alert.type === 'error' ? '🚨' : alert.type === 'warning' ? 'âš ī¸' : 'â„šī¸'; return `
${icon}
${alert.message}
Action: ${alert.action}
`; }).join(''); } function formatDate(dateString) { if (!dateString) return 'Never'; return new Date(dateString).toLocaleDateString() + ' ' + new Date(dateString).toLocaleTimeString(); } function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `fixed top-4 right-4 p-4 rounded-lg shadow-md z-50 ${ type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500' } text-white`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 5000); } // Add CSS for tab styling const style = document.createElement('style'); style.textContent = ` .admin-tab { padding: 0.5rem 1rem; border-bottom: 2px solid transparent; font-medium: 500; text-decoration: none; transition: all 0.2s; } .admin-tab:hover { text-decoration: none; border-bottom-color: #d1d5db; } .admin-tab.active { border-bottom-color: #3b82f6; color: #3b82f6; } `; document.head.appendChild(style);