Files
GopherGate/static/js/pages/analytics.js

184 lines
5.5 KiB
JavaScript

// Analytics Page Module
class AnalyticsPage {
constructor() {
this.filters = {
dateRange: '7d',
client: 'all',
provider: 'all'
};
this.init();
}
async init() {
// Load data
await Promise.all([
this.loadClients(),
this.loadCharts(),
this.loadUsageData()
]);
// Setup event listeners
this.setupEventListeners();
}
async loadClients() {
try {
const clients = await window.api.get('/clients');
this.renderClientFilter(clients);
} catch (error) {
console.error('Error loading clients for filter:', error);
}
}
renderClientFilter(clients) {
const select = document.getElementById('client-filter');
if (!select) return;
// Clear existing options except "All Clients"
while (select.options.length > 1) {
select.remove(1);
}
// Add client options
clients.forEach(client => {
const option = document.createElement('option');
option.value = client.id;
option.textContent = client.name || client.id;
select.appendChild(option);
});
}
async loadCharts() {
try {
const breakdown = await window.api.get('/analytics/breakdown');
const timeSeries = await window.api.get('/usage/time-series');
this.renderAnalyticsChart(timeSeries.series);
this.renderClientsChart(breakdown.clients);
this.renderModelsChart(breakdown.models);
} catch (error) {
console.error('Error loading analytics charts:', error);
}
}
renderAnalyticsChart(series) {
const data = {
labels: series.map(s => s.time),
datasets: [
{
label: 'Requests',
data: series.map(s => s.requests),
color: '#fe8019', // orange
fill: true
},
{
label: 'Tokens',
data: series.map(s => s.tokens),
color: '#b8bb26', // green
fill: true,
hidden: true
}
]
};
window.chartManager.createLineChart('analytics-chart', data);
}
renderClientsChart(clients) {
const data = {
labels: clients.map(c => c.label),
datasets: [{
label: 'Requests',
data: clients.map(c => c.value),
color: '#83a598' // blue
}]
};
window.chartManager.createHorizontalBarChart('clients-chart', data);
}
renderModelsChart(models) {
const data = {
labels: models.map(m => m.label),
data: models.map(m => m.value),
colors: window.chartManager.defaultColors
};
window.chartManager.createDoughnutChart('models-chart', data);
}
async loadUsageData() {
try {
const usageData = await window.api.get('/usage/detailed');
this.renderUsageTable(usageData);
} catch (error) {
console.error('Error loading usage data:', error);
}
}
renderUsageTable(data) {
const tableBody = document.querySelector('#usage-table tbody');
if (!tableBody) return;
if (data.length === 0) {
tableBody.innerHTML = '<tr><td colspan="7" class="text-center">No historical data found</td></tr>';
return;
}
tableBody.innerHTML = data.map(row => `
<tr>
<td>${row.date}</td>
<td><span class="badge-client">${row.client}</span></td>
<td>${row.provider}</td>
<td><code class="code-sm">${row.model}</code></td>
<td>${row.requests.toLocaleString()}</td>
<td>${window.api.formatNumber(row.tokens)}</td>
<td>${window.api.formatCurrency(row.cost)}</td>
</tr>
`).join('');
}
setupEventListeners() {
const refreshBtn = document.getElementById('refresh-analytics');
if (refreshBtn) {
refreshBtn.onclick = () => this.refresh();
}
// Export button
const exportBtn = document.getElementById('export-data');
if (exportBtn) {
exportBtn.onclick = () => this.exportData();
}
}
async exportData() {
// Simple CSV export
const data = await window.api.get('/usage/detailed');
if (!data || data.length === 0) return;
const headers = Object.keys(data[0]).join(',');
const rows = data.map(obj => Object.values(obj).join(',')).join('\n');
const csv = `${headers}\n${rows}`;
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `llm-proxy-usage-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
refresh() {
this.loadCharts();
this.loadUsageData();
window.authManager.showToast('Analytics data refreshed', 'success');
}
}
window.initAnalytics = async () => {
window.analyticsPage = new AnalyticsPage();
};