2026-04-15-4
This commit is contained in:
+110
-22
@@ -13,50 +13,138 @@ const analytics = {
|
||||
{ name: 'Sun', requests: 3490, latency: 225 },
|
||||
],
|
||||
|
||||
init: () => {
|
||||
console.log('📈 Initializing Analytics...');
|
||||
analytics.renderVolumeChart();
|
||||
analytics.renderLatencyChart();
|
||||
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 {
|
||||
analytics.renderPlaceholder();
|
||||
}
|
||||
|
||||
analytics.renderLatencyChart();
|
||||
},
|
||||
|
||||
renderVolumeChart: () => {
|
||||
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.toLocaleDateString('en-US', { weekday: 'short' }),
|
||||
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(...analytics.mockData.map(d => d.requests));
|
||||
const max = Math.max(...data.map(d => d.requests), 1);
|
||||
|
||||
container.innerHTML = analytics.mockData.map(d => `
|
||||
<div class="flex-1 flex flex-col items-center gap-2 group">
|
||||
<div class="w-full bg-blue-600/20 rounded-t-lg relative overflow-hidden flex items-end" style="height: 100%">
|
||||
<div class="w-full bg-gradient-to-t from-blue-600 to-blue-400 rounded-t-lg transition-all duration-700 hover:brightness-125"
|
||||
style="height: ${(d.requests / max) * 100}%">
|
||||
container.innerHTML = data.map((d, i) => `
|
||||
<div class="flex-1 flex flex-col items-center gap-4 group h-full">
|
||||
<div class="flex-1 w-full bg-slate-900/40 rounded-2xl relative overflow-hidden flex items-end p-1 border border-white/[0.03] backdrop-blur-sm">
|
||||
<div class="w-full bg-gradient-to-t from-blue-600 via-blue-500 to-cyan-400 rounded-xl transition-all duration-1000 ease-out hover:brightness-125 shadow-[0_0_30px_rgba(37,99,235,0.2)]"
|
||||
style="height: 0%; transition-delay: ${i * 50}ms">
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.group h-full div[style*="height: 0%"]')[0].style.height = "${(d.requests / max) * 100}%";
|
||||
}, 100);
|
||||
</script>
|
||||
</div>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span class="text-[10px] font-bold bg-slate-900 px-2 py-1 rounded border border-slate-800">${d.requests}</span>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-all transform translate-y-2 group-hover:translate-y-0 z-10">
|
||||
<span class="text-[10px] font-black bg-blue-600 text-white px-3 py-1.5 rounded-lg shadow-2xl border border-blue-400/30 mb-2">${d.requests.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold text-slate-500">${d.name}</span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-widest">${d.name}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Trigger animations after render
|
||||
setTimeout(() => {
|
||||
container.querySelectorAll('.w-full.bg-gradient-to-t').forEach((el, index) => {
|
||||
const height = el.parentElement.parentElement.dataset.height;
|
||||
el.style.height = el.getAttribute('data-target-height');
|
||||
});
|
||||
}, 100);
|
||||
},
|
||||
|
||||
renderPlaceholder: () => {
|
||||
const container = document.getElementById('v-chart');
|
||||
if (container) container.innerHTML = `
|
||||
<div class="w-full h-full flex flex-col items-center justify-center text-slate-500 gap-4">
|
||||
<div class="w-16 h-16 rounded-full bg-slate-900 flex items-center justify-center opacity-50">
|
||||
<i data-lucide="bar-chart" class="w-8 h-8"></i>
|
||||
</div>
|
||||
<p class="text-xs italic font-bold tracking-widest uppercase opacity-40">No activity recorded for this period</p>
|
||||
</div>`;
|
||||
if (window.lucide) lucide.createIcons();
|
||||
},
|
||||
|
||||
renderLatencyChart: () => {
|
||||
const container = document.getElementById('l-chart');
|
||||
if (!container) return;
|
||||
|
||||
const max = Math.max(...analytics.mockData.map(d => d.latency));
|
||||
const mockLatency = [
|
||||
{ name: 'Mon', latency: 240 },
|
||||
{ name: 'Tue', latency: 198 },
|
||||
{ name: 'Wed', latency: 310 },
|
||||
{ name: 'Thu', latency: 208 },
|
||||
{ name: 'Fri', latency: 250 },
|
||||
{ name: 'Sat', latency: 210 },
|
||||
{ name: 'Sun', latency: 225 },
|
||||
];
|
||||
|
||||
const max = Math.max(...mockLatency.map(d => d.latency));
|
||||
|
||||
container.innerHTML = analytics.mockData.map(d => `
|
||||
<div class="flex-1 flex flex-col items-center gap-2 group">
|
||||
<div class="w-full bg-violet-600/10 rounded-t-lg relative overflow-hidden flex items-end" style="height: 100%">
|
||||
<div class="w-full bg-gradient-to-t from-violet-600 to-violet-400 rounded-t-lg transition-all duration-700 hover:brightness-125"
|
||||
container.innerHTML = mockLatency.map((d, i) => `
|
||||
<div class="flex-1 flex flex-col items-center gap-4 group h-full">
|
||||
<div class="flex-1 w-full bg-slate-900/40 rounded-2xl relative overflow-hidden flex items-end p-1 border border-white/[0.03] backdrop-blur-sm">
|
||||
<div class="w-full bg-gradient-to-t from-violet-600 via-violet-500 to-fuchsia-400 rounded-xl transition-all duration-1000 ease-out hover:brightness-125 shadow-[0_0_30px_rgba(139,92,246,0.2)]"
|
||||
style="height: ${(d.latency / max) * 100}%">
|
||||
</div>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span class="text-[10px] font-bold bg-slate-900 px-2 py-1 rounded border border-slate-800">${d.latency}ms</span>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-all transform translate-y-2 group-hover:translate-y-0 z-10">
|
||||
<span class="text-[10px] font-black bg-violet-600 text-white px-3 py-1.5 rounded-lg shadow-2xl border border-violet-400/30 mb-2">${d.latency}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-[10px] font-bold text-slate-500">${d.name}</span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-widest">${d.name}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user