440 lines
16 KiB
JavaScript
440 lines
16 KiB
JavaScript
// Main Dashboard Controller
|
|
|
|
class Dashboard {
|
|
constructor() {
|
|
this.currentPage = 'overview';
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
if (!window.authManager || !window.authManager.isAuthenticated) {
|
|
return;
|
|
}
|
|
|
|
this.setupNavigation();
|
|
this.setupSidebar();
|
|
this.setupRefresh();
|
|
this.updateTime();
|
|
|
|
// Load initial page from hash or default to overview
|
|
const initialPage = window.location.hash.substring(1) || 'overview';
|
|
this.loadPage(initialPage);
|
|
|
|
setInterval(() => this.updateTime(), 1000);
|
|
}
|
|
|
|
setupNavigation() {
|
|
const menuItems = document.querySelectorAll('.menu-item');
|
|
menuItems.forEach(item => {
|
|
item.onclick = (e) => {
|
|
e.preventDefault();
|
|
const page = item.getAttribute('data-page') || item.getAttribute('href').substring(1);
|
|
this.loadPage(page);
|
|
};
|
|
});
|
|
|
|
window.onhashchange = () => {
|
|
const page = window.location.hash.substring(1) || 'overview';
|
|
if (page !== this.currentPage) {
|
|
this.loadPage(page);
|
|
}
|
|
};
|
|
}
|
|
|
|
setupSidebar() {
|
|
const toggleBtn = document.getElementById('sidebar-toggle');
|
|
const sidebar = document.querySelector('.sidebar');
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
|
|
if (toggleBtn && sidebar) {
|
|
toggleBtn.onclick = () => {
|
|
sidebar.classList.toggle('collapsed');
|
|
localStorage.setItem('sidebar_collapsed', sidebar.classList.contains('collapsed'));
|
|
};
|
|
|
|
if (localStorage.getItem('sidebar_collapsed') === 'true') {
|
|
sidebar.classList.add('collapsed');
|
|
}
|
|
}
|
|
|
|
if (logoutBtn) {
|
|
logoutBtn.onclick = () => {
|
|
window.authManager.logout();
|
|
};
|
|
}
|
|
}
|
|
|
|
setupRefresh() {
|
|
const refreshBtn = document.getElementById('refresh-btn');
|
|
if (refreshBtn) {
|
|
refreshBtn.onclick = () => this.refreshCurrentPage();
|
|
}
|
|
}
|
|
|
|
updateTime() {
|
|
const timeElement = document.getElementById('current-time');
|
|
if (timeElement) {
|
|
timeElement.textContent = luxon.DateTime.now().toFormat('HH:mm:ss');
|
|
}
|
|
}
|
|
|
|
async loadPage(page) {
|
|
if (window.chartManager) {
|
|
window.chartManager.destroyAllCharts();
|
|
}
|
|
|
|
this.currentPage = page;
|
|
window.location.hash = page;
|
|
window.scrollTo(0, 0);
|
|
|
|
// Update menu active state
|
|
document.querySelectorAll('.menu-item').forEach(item => {
|
|
item.classList.toggle('active', (item.dataset.page || item.getAttribute('href').substring(1)) === page);
|
|
});
|
|
|
|
const titleElement = document.getElementById('page-title');
|
|
const titles = {
|
|
'overview': 'Overview',
|
|
'analytics': 'Analytics',
|
|
'costs': 'Costs',
|
|
'clients': 'Clients',
|
|
'providers': 'Providers',
|
|
'monitoring': 'Monitoring',
|
|
'settings': 'Settings',
|
|
'logs': 'Logs'
|
|
};
|
|
if (titleElement) titleElement.textContent = titles[page] || 'Dashboard';
|
|
|
|
this.showLoading();
|
|
|
|
try {
|
|
const content = document.getElementById('page-content');
|
|
if (content) {
|
|
content.innerHTML = await this.getPageTemplate(page);
|
|
await this.initializePageScript(page);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error loading page ${page}:`, error);
|
|
this.showError(`Failed to load ${page}`);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
showLoading() {
|
|
const content = document.getElementById('page-content');
|
|
if (content) content.classList.add('loading');
|
|
}
|
|
|
|
hideLoading() {
|
|
const content = document.getElementById('page-content');
|
|
if (content) content.classList.remove('loading');
|
|
}
|
|
|
|
async getPageTemplate(page) {
|
|
// Return templates directly based on the page name
|
|
switch (page) {
|
|
case 'overview': return this.getOverviewTemplate();
|
|
case 'clients': return this.getClientsTemplate();
|
|
case 'providers': return this.getProvidersTemplate();
|
|
case 'models': return this.getModelsTemplate();
|
|
case 'logs': return this.getLogsTemplate();
|
|
case 'monitoring': return this.getMonitoringTemplate();
|
|
case 'settings': return '<div class="loading-placeholder">Loading settings...</div>';
|
|
case 'analytics': return this.getAnalyticsTemplate();
|
|
case 'costs': return this.getCostsTemplate();
|
|
default: return '<div class="empty-state"><h3>Page not found</h3></div>';
|
|
}
|
|
}
|
|
|
|
async initializePageScript(page) {
|
|
const initFn = `init${page.charAt(0).toUpperCase() + page.slice(1)}`;
|
|
if (typeof window[initFn] === 'function') {
|
|
await window[initFn]();
|
|
}
|
|
}
|
|
|
|
refreshCurrentPage() {
|
|
const refreshBtn = document.getElementById('refresh-btn');
|
|
if (refreshBtn) {
|
|
refreshBtn.classList.add('fa-spin');
|
|
setTimeout(() => refreshBtn.classList.remove('fa-spin'), 1000);
|
|
}
|
|
this.loadPage(this.currentPage);
|
|
}
|
|
|
|
showError(message) {
|
|
const content = document.getElementById('page-content');
|
|
if (content) {
|
|
content.innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<h3>Error</h3>
|
|
<p>${message}</p>
|
|
<button class="btn btn-primary" onclick="window.dashboard.loadPage('overview')">Return to Overview</button>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Templates
|
|
getOverviewTemplate() {
|
|
return `
|
|
<div class="stats-grid" id="overview-stats"></div>
|
|
<div class="grid-2">
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Request Volume (Last 24h)</h3>
|
|
<canvas id="requests-chart" height="300"></canvas>
|
|
</div>
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Provider Share</h3>
|
|
<canvas id="providers-chart" height="300"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<h3 class="card-title">System Health</h3>
|
|
<div id="system-health" style="margin-top: 1rem;"></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Recent Activity</h3>
|
|
<button class="card-action-btn" onclick="window.overviewPage.loadRecentRequests()"><i class="fas fa-redo"></i></button>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="table" id="recent-requests">
|
|
<thead>
|
|
<tr><th>Time</th><th>Client</th><th>Provider</th><th>Model</th><th>Tokens</th><th>Status</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getClientsTemplate() {
|
|
return `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div>
|
|
<h3 class="card-title">API Clients</h3>
|
|
<p class="card-subtitle">Manage tokens and access</p>
|
|
</div>
|
|
<button class="btn btn-primary" id="add-client">
|
|
<i class="fas fa-plus"></i> Create Client
|
|
</button>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="table" id="clients-table">
|
|
<thead>
|
|
<tr><th>ID</th><th>Name</th><th>Token</th><th>Created</th><th>Last Used</th><th>Requests</th><th>Status</th><th>Actions</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Usage by Client</h3>
|
|
<canvas id="client-usage-chart" height="250"></canvas>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getProvidersTemplate() {
|
|
return `
|
|
<div class="stats-grid" id="provider-stats"></div>
|
|
<div id="providers-list"></div>
|
|
<div class="form-actions" style="margin-top: 2rem;">
|
|
<button class="btn btn-secondary" id="test-all-providers">
|
|
<i class="fas fa-vial"></i> Test All Connections
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getModelsTemplate() {
|
|
return `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div>
|
|
<h3 class="card-title">Model Registry</h3>
|
|
<p class="card-subtitle">Manage model availability and custom pricing</p>
|
|
</div>
|
|
<div class="card-actions">
|
|
<input type="text" id="model-search" placeholder="Search models..." class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: 250px;">
|
|
</div>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="table" id="models-table">
|
|
<thead>
|
|
<tr><th>ID</th><th>Display Name</th><th>Provider</th><th>Pricing (In/Out)</th><th>Context</th><th>Status</th><th>Actions</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getLogsTemplate() {
|
|
return `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Detailed Request Logs</h3>
|
|
<div class="card-actions">
|
|
<select id="log-filter" class="form-control" style="margin-bottom: 0; padding: 4px 8px;">
|
|
<option value="all">All Status</option>
|
|
<option value="success">Success</option>
|
|
<option value="error">Errors</option>
|
|
</select>
|
|
<input type="text" id="log-search" placeholder="Search logs..." class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: 200px;">
|
|
</div>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="table" id="logs-table">
|
|
<thead>
|
|
<tr><th>Timestamp</th><th>Status</th><th>Context</th><th>Request Details</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getAnalyticsTemplate() {
|
|
return `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div>
|
|
<h3 class="card-title">Usage Analytics</h3>
|
|
<p class="card-subtitle">Request volume and token distribution</p>
|
|
</div>
|
|
<div class="card-actions">
|
|
<button class="btn btn-secondary" id="export-data">
|
|
<i class="fas fa-download"></i> Export CSV
|
|
</button>
|
|
<button class="btn btn-primary" id="refresh-analytics">
|
|
<i class="fas fa-sync"></i> Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container" style="height: 400px;">
|
|
<canvas id="analytics-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-2">
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Volume by Client</h3>
|
|
<canvas id="clients-chart" height="300"></canvas>
|
|
</div>
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Model Distribution</h3>
|
|
<canvas id="models-chart" height="300"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Historical Usage Log</h3>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="table" id="usage-table">
|
|
<thead>
|
|
<tr><th>Date</th><th>Client</th><th>Provider</th><th>Model</th><th>Requests</th><th>Tokens</th><th>Cost</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getCostsTemplate() {
|
|
return `
|
|
<div class="stats-grid" id="cost-stats"></div>
|
|
|
|
<div class="grid-2">
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Provider Spending</h3>
|
|
<canvas id="costs-chart" height="300"></canvas>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Budget Tracking</h3>
|
|
</div>
|
|
<div class="card-body" id="budget-progress">
|
|
<!-- Budget progress bars -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Active Model Pricing</h3>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="table" id="pricing-table">
|
|
<thead>
|
|
<tr><th>Provider</th><th>Model</th><th>Input Cost</th><th>Output Cost</th><th>Last Updated</th></tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getMonitoringTemplate() {
|
|
return `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div>
|
|
<h3 class="card-title">Real-time Stream</h3>
|
|
<p class="card-subtitle">Live request activity and system metrics</p>
|
|
</div>
|
|
<button class="btn btn-secondary" id="pause-monitoring">
|
|
<i class="fas fa-pause"></i> Pause Stream
|
|
</button>
|
|
</div>
|
|
<div class="grid-2" style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem;">
|
|
<div>
|
|
<h4>Incoming Requests</h4>
|
|
<div id="request-stream" class="monitoring-stream" style="height: 400px; overflow-y: auto; background: #f8fafc; border-radius: 8px; margin-top: 1rem; padding: 1rem;"></div>
|
|
</div>
|
|
<div>
|
|
<h4>System Performance</h4>
|
|
<div id="system-metrics"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="grid-3" style="margin-top: 1.5rem;">
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Latency (ms)</h3>
|
|
<canvas id="response-time-chart" height="200"></canvas>
|
|
</div>
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Error Rate (%)</h3>
|
|
<canvas id="error-rate-chart" height="200"></canvas>
|
|
</div>
|
|
<div class="chart-container">
|
|
<h3 class="card-title">Rate Limiting</h3>
|
|
<canvas id="rate-limit-chart" height="200"></canvas>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.initDashboard = () => {
|
|
window.dashboard = new Dashboard();
|
|
};
|
|
if (window.authManager && window.authManager.isAuthenticated) {
|
|
window.initDashboard();
|
|
}
|
|
});
|