Files
GopherGate/static/js/pages/clients.js
hobokenchicken 2c5a6a596b ui: major UX polish and bug fixes for dashboard
- Added global loading spinner and page transitions.
- Improved sidebar with tooltips and persistent collapsed state.
- Fixed chart memory leaks by properly destroying instances on page change.
- Unified WebSocket event handling and status indicators.
- Refined stat cards, tables, and modal interactions.
- Added real backend integration for logout and session management.
2026-02-26 15:48:01 -05:00

180 lines
6.7 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');
if (!data || data.length === 0) {
const canvas = document.getElementById('client-usage-chart');
if (canvas) canvas.closest('.chart-container').style.display = 'none';
return;
}
const chartData = {
labels: data.map(item => item.client_id),
datasets: [{
label: 'Requests',
data: data.map(item => item.requests),
color: '#6366f1'
}]
};
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();
};