- Switched from mock data to real backend APIs. - Implemented unified ApiClient for consistent frontend data fetching. - Refactored dashboard structure and styles for a modern SaaS aesthetic. - Fixed Axum 0.8+ routing and parameter syntax issues. - Implemented real client creation/deletion and provider health monitoring. - Synchronized WebSocket event structures between backend and frontend.
174 lines
6.4 KiB
JavaScript
174 lines
6.4 KiB
JavaScript
// Clients Page Module
|
|
|
|
class ClientsPage {
|
|
constructor() {
|
|
this.clients = [];
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
// Load data
|
|
await Promise.all([
|
|
this.loadClients(),
|
|
this.loadClientUsageChart()
|
|
]);
|
|
|
|
// Setup event listeners
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
async loadClients() {
|
|
try {
|
|
const data = await window.api.get('/clients');
|
|
this.clients = data;
|
|
this.renderClientsTable();
|
|
} catch (error) {
|
|
console.error('Error loading clients:', error);
|
|
window.authManager.showToast('Failed to load clients', 'error');
|
|
}
|
|
}
|
|
|
|
renderClientsTable() {
|
|
const tableBody = document.querySelector('#clients-table tbody');
|
|
if (!tableBody) return;
|
|
|
|
if (this.clients.length === 0) {
|
|
tableBody.innerHTML = '<tr><td colspan="8" class="text-center">No clients configured</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tableBody.innerHTML = this.clients.map(client => {
|
|
const statusClass = client.status === 'active' ? 'success' : 'secondary';
|
|
const statusIcon = client.status === 'active' ? 'check-circle' : 'clock';
|
|
const created = luxon.DateTime.fromISO(client.created_at).toFormat('MMM dd, yyyy');
|
|
|
|
return `
|
|
<tr>
|
|
<td><span class="badge-client">${client.id}</span></td>
|
|
<td><strong>${client.name}</strong></td>
|
|
<td>
|
|
<code class="token-display">sk-••••${client.id.substring(client.id.length - 4)}</code>
|
|
</td>
|
|
<td>${created}</td>
|
|
<td>${client.last_used ? window.api.formatTimeAgo(client.last_used) : 'Never'}</td>
|
|
<td>${client.requests_count.toLocaleString()}</td>
|
|
<td>
|
|
<span class="status-badge ${statusClass}">
|
|
<i class="fas fa-${statusIcon}"></i>
|
|
${client.status}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="btn-action" title="Edit" onclick="window.clientsPage.editClient('${client.id}')">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn-action danger" title="Delete" onclick="window.clientsPage.deleteClient('${client.id}')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
async loadClientUsageChart() {
|
|
try {
|
|
const data = await window.api.get('/usage/clients');
|
|
|
|
const chartData = {
|
|
labels: data.map(item => item.client_id),
|
|
datasets: [{
|
|
label: 'Requests',
|
|
data: data.map(item => item.requests),
|
|
color: '#3b82f6'
|
|
}]
|
|
};
|
|
|
|
window.chartManager.createHorizontalBarChart('client-usage-chart', chartData);
|
|
|
|
} catch (error) {
|
|
console.error('Error loading client usage chart:', error);
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const addBtn = document.getElementById('add-client');
|
|
if (addBtn) {
|
|
addBtn.onclick = () => this.showAddClientModal();
|
|
}
|
|
}
|
|
|
|
showAddClientModal() {
|
|
const modal = document.createElement('div');
|
|
modal.className = 'modal active';
|
|
modal.innerHTML = `
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Create New API Client</h3>
|
|
<button class="modal-close" onclick="this.closest('.modal').remove()">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-control">
|
|
<label for="new-client-name">Display Name</label>
|
|
<input type="text" id="new-client-name" placeholder="e.g. My Coding Assistant" required>
|
|
</div>
|
|
<div class="form-control">
|
|
<label for="new-client-id">Custom ID (Optional)</label>
|
|
<input type="text" id="new-client-id" placeholder="e.g. personal-app">
|
|
<small>Leave empty to generate automatically</small>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="this.closest('.modal').remove()">Cancel</button>
|
|
<button class="btn btn-primary" id="confirm-create-client">Create Client</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
modal.querySelector('#confirm-create-client').onclick = async () => {
|
|
const name = modal.querySelector('#new-client-name').value;
|
|
const id = modal.querySelector('#new-client-id').value;
|
|
|
|
if (!name) {
|
|
window.authManager.showToast('Name is required', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await window.api.post('/clients', { name, client_id: id || null });
|
|
window.authManager.showToast(`Client "${name}" created`, 'success');
|
|
modal.remove();
|
|
this.loadClients();
|
|
} catch (error) {
|
|
window.authManager.showToast(error.message, 'error');
|
|
}
|
|
};
|
|
}
|
|
|
|
async deleteClient(id) {
|
|
if (!confirm(`Are you sure you want to delete client ${id}? This cannot be undone.`)) return;
|
|
|
|
try {
|
|
await window.api.delete(`/clients/${id}`);
|
|
window.authManager.showToast('Client deleted', 'success');
|
|
this.loadClients();
|
|
} catch (error) {
|
|
window.authManager.showToast(error.message, 'error');
|
|
}
|
|
}
|
|
|
|
editClient(id) {
|
|
window.authManager.showToast('Edit client not implemented yet', 'info');
|
|
}
|
|
}
|
|
|
|
window.initClients = async () => {
|
|
window.clientsPage = new ClientsPage();
|
|
};
|