Files
GopherGate/static/js/pages/logs.js
hobokenchicken 686163780c feat: major dashboard overhaul and polish
- 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.
2026-02-26 15:40:12 -05:00

124 lines
4.0 KiB
JavaScript

// Logs Page Module
class LogsPage {
constructor() {
this.logs = [];
this.init();
}
async init() {
await this.loadLogs();
this.setupEventListeners();
}
async loadLogs() {
const tableBody = document.querySelector('#logs-table tbody');
if (tableBody) tableBody.innerHTML = '<tr><td colspan="4" class="text-center">Loading logs...</td></tr>';
try {
const data = await window.api.get('/system/logs');
this.logs = data;
this.renderLogs();
} catch (error) {
console.error('Error loading logs:', error);
window.authManager.showToast('Failed to load system logs', 'error');
}
}
renderLogs() {
const tableBody = document.querySelector('#logs-table tbody');
if (!tableBody) return;
if (this.logs.length === 0) {
tableBody.innerHTML = '<tr><td colspan="4" class="text-center">No logs found</td></tr>';
return;
}
tableBody.innerHTML = this.logs.map(log => {
const statusClass = log.status === 'success' ? 'success' : 'danger';
const timestamp = luxon.DateTime.fromISO(log.timestamp).toFormat('yyyy-MM-dd HH:mm:ss');
return `
<tr class="log-row">
<td class="whitespace-nowrap">${timestamp}</td>
<td>
<span class="status-badge ${statusClass}">
${log.status.toUpperCase()}
</span>
</td>
<td>
<div class="log-meta">
<span class="badge-client">${log.client_id}</span>
<span class="log-provider">${log.provider}</span>
</div>
</td>
<td>
<div class="log-message-container">
<code class="log-model">${log.model}</code>
<span class="log-tokens">${log.tokens} tokens</span>
<span class="log-duration">${log.duration}ms</span>
${log.error ? `<div class="log-error-msg">${log.error}</div>` : ''}
</div>
</td>
</tr>
`;
}).join('');
}
setupEventListeners() {
const refreshBtn = document.getElementById('refresh-btn');
if (refreshBtn) {
// Already handled by dashboard.js but we can add more specific logic if needed
}
const logFilter = document.getElementById('log-filter');
if (logFilter) {
logFilter.onchange = () => this.filterLogs();
}
const logSearch = document.getElementById('log-search');
if (logSearch) {
logSearch.oninput = (e) => this.searchLogs(e.target.value);
}
}
filterLogs() {
const filter = document.getElementById('log-filter').value;
if (filter === 'all') {
this.renderLogs();
return;
}
const filtered = this.logs.filter(log => log.status === (filter === 'error' ? 'error' : 'success'));
this.renderFilteredLogs(filtered);
}
searchLogs(query) {
if (!query) {
this.renderLogs();
return;
}
const q = query.toLowerCase();
const filtered = this.logs.filter(log =>
log.client_id.toLowerCase().includes(q) ||
log.model.toLowerCase().includes(q) ||
log.provider.toLowerCase().includes(q) ||
(log.error && log.error.toLowerCase().includes(q))
);
this.renderFilteredLogs(filtered);
}
renderFilteredLogs(filteredLogs) {
// reuse same rendering logic or similar
const originalLogs = this.logs;
this.logs = filteredLogs;
this.renderLogs();
this.logs = originalLogs;
}
}
window.initLogs = async () => {
window.logsPage = new LogsPage();
};