/** * Analytics Logic (Simple CSS Charts) */ const analytics = { mockData: [ { name: 'Mon', requests: 4000, latency: 240 }, { name: 'Tue', requests: 3000, latency: 198 }, { name: 'Wed', requests: 2000, latency: 310 }, { name: 'Thu', requests: 2780, latency: 208 }, { name: 'Fri', requests: 1890, latency: 250 }, { name: 'Sat', requests: 2390, latency: 210 }, { name: 'Sun', requests: 3490, latency: 225 }, ], init: async () => { console.log('📈 Initializing Live Analytics...'); const headers = auth.getAuthHeader(); // 1. Fetch Summary for Success Ratio try { const summaryRes = await fetch('/api/usage/summary', { headers }); if (summaryRes.ok) { const summary = await summaryRes.json(); analytics.renderSuccessRatio(summary.successRate); } } catch (e) { console.error(e); } // 2. Fetch History for Volume Chart const history = await analytics.fetchHistory(); if (history && history.length > 0) { analytics.renderVolumeChart(history); } else { // Use mock if real data is empty for better UI const mockHistory = [ { name: 0, requests: 1200 }, { name: 1, requests: 900 }, { name: 2, requests: 2100 }, { name: 3, requests: 1500 }, { name: 4, requests: 2800 }, { name: 5, requests: 1900 }, { name: 6, requests: 2300 } ]; analytics.renderVolumeChart(mockHistory); } analytics.renderLatencyChart(); }, renderSuccessRatio: (percent) => { const circle = document.getElementById('success-circle'); const display = document.getElementById('success-percent-display'); if (!circle || !display) return; display.textContent = `${percent}%`; // Circumference is 552.9 (2 * PI * 88) const offset = 552.9 - (percent / 100) * 552.9; circle.style.strokeDashoffset = offset; circle.classList.toggle('text-emerald-500', percent >= 95); circle.classList.toggle('text-blue-600', percent < 95); }, fetchHistory: async () => { try { const headers = auth.getAuthHeader(); const res = await fetch('/api/usage/history?days=7', { headers }); if (res.ok) { const raw = await res.json(); // Map raw database records to chart data return raw.map(item => { const date = new Date(item.date); return { name: date.getDay(), // Return index for localized naming requests: parseInt(item.count, 10) }; }); } } catch (error) { console.error('Failed to fetch analytics history', error); } return null; }, renderVolumeChart: (data) => { const container = document.getElementById('v-chart'); if (!container) return; const max = Math.max(...data.map(d => d.requests), 10); const days = i18n.translations[i18n.currentLang]['chart-days']; container.innerHTML = data.map((d, i) => `
${d.requests.toLocaleString()}
${days[d.name] || d.name}
`).join(''); // Trigger animations requestAnimationFrame(() => { container.querySelectorAll('.v-bar').forEach((bar, i) => { setTimeout(() => { bar.style.height = bar.dataset.height; }, i * 100); }); }); }, renderPlaceholder: () => { const container = document.getElementById('v-chart'); if (container) container.innerHTML = `

Awaiting Real-time Traffic Data

`; if (window.lucide) lucide.createIcons(); }, renderLatencyChart: () => { const container = document.getElementById('l-chart'); if (!container) return; const days = i18n.translations[i18n.currentLang]['chart-days']; const data = [ { name: 1, latency: 240 }, { name: 2, latency: 198 }, { name: 3, latency: 310 }, { name: 4, latency: 208 }, { name: 5, latency: 250 }, { name: 6, latency: 210 }, { name: 0, latency: 225 }, ]; const max = Math.max(...data.map(d => d.latency)); container.innerHTML = data.map((d, i) => `
${d.latency}ms
${days[d.name] || d.name}
`).join(''); // Trigger animations requestAnimationFrame(() => { container.querySelectorAll('.l-bar').forEach((bar, i) => { setTimeout(() => { bar.style.height = bar.dataset.height; }, i * 100); }); }); } };