- 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.
124 lines
4.0 KiB
JavaScript
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();
|
|
};
|