agentic-os/dashboard/pages/errors.js

158 lines
7.3 KiB
JavaScript

async function renderErrors() {
const content = document.getElementById('pageContent');
content.innerHTML = `
<div class="page-header">
<div class="page-header-left">
<div class="page-title">Error Dashboard</div>
<div class="page-subtitle">Track and manage system errors across all agents</div>
</div>
<div class="btn-group">
<button class="btn btn-ghost" onclick="refreshErrors()">🔄 Refresh</button>
<button class="btn btn-danger" onclick="clearAllErrors()">🗑 Clear All</button>
</div>
</div>
<div class="flex gap-3 mb-3" style="flex-wrap:wrap">
<div class="metric-tile" style="flex:1;min-width:120px">
<div class="metric-tile-value" id="errorTotalCount">0</div>
<div class="metric-tile-label">Total Errors</div>
</div>
<div class="metric-tile" style="flex:1;min-width:120px">
<div class="metric-tile-value" id="errorAgentCount">0</div>
<div class="metric-tile-label">Agent Errors</div>
</div>
<div class="metric-tile" style="flex:1;min-width:120px">
<div class="metric-tile-value" id="errorSkillCount">0</div>
<div class="metric-tile-label">Skill Errors</div>
</div>
<div class="metric-tile" style="flex:1;min-width:120px">
<div class="metric-tile-value" id="errorCircuitCount">0</div>
<div class="metric-tile-label">Circuit Breaks</div>
</div>
</div>
<div class="flex gap-2 mb-3" style="flex-wrap:wrap">
<select id="errorCategoryFilter" class="form-select" style="width:160px" onchange="refreshErrors()">
<option value="">All Categories</option>
<option value="agent">Agent</option>
<option value="skill">Skill</option>
<option value="api">API</option>
<option value="system">System</option>
<option value="general">General</option>
</select>
<span id="errorCountBadge" class="badge badge-danger" style="display:none"></span>
</div>
<div id="errorList"><div class="loading"><div class="loading-spinner"></div></div></div>
<div class="section-title mt-4">Circuit Breaker Status</div>
<div id="circuitBreakerCards" class="grid grid-3"></div>
`;
await Promise.all([refreshErrors(), loadCircuitBreaker()]);
}
async function refreshErrors() {
const container = document.getElementById('errorList');
if (!container) return;
const category = document.getElementById('errorCategoryFilter')?.value || '';
try {
const data = await api.getErrors(100, category);
const errors = data.errors || [];
const totalEl = document.getElementById('errorTotalCount');
if (totalEl) totalEl.textContent = errors.length;
const agentErrors = errors.filter(e => e.category === 'agent').length;
const skillErrors = errors.filter(e => e.category === 'skill').length;
const circuitErrors = errors.filter(e => e.category === 'circuit').length;
const aEl = document.getElementById('errorAgentCount');
if (aEl) aEl.textContent = agentErrors;
const sEl = document.getElementById('errorSkillCount');
if (sEl) sEl.textContent = skillErrors;
const cEl = document.getElementById('errorCircuitCount');
if (cEl) cEl.textContent = circuitErrors;
const badge = document.getElementById('errorCountBadge');
if (badge) {
if (errors.length > 0) { badge.style.display = 'inline'; badge.textContent = errors.length + ' issues'; }
else { badge.style.display = 'none'; }
}
if (errors.length === 0) {
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">✅</div><div class="empty-state-title">No errors</div><div class="empty-state-desc">System is running smoothly</div></div>';
return;
}
container.innerHTML = `
<div class="table-wrapper">
<table>
<thead><tr><th>Time</th><th>Category</th><th>Source</th><th>Message</th><th>ID</th></tr></thead>
<tbody>
${errors.slice().reverse().map(e => `
<tr>
<td style="font-size:12px;white-space:nowrap">${formatDate(e.timestamp)}</td>
<td><span class="badge ${e.category === 'agent' ? 'badge-danger' : e.category === 'skill' ? 'badge-warning' : e.category === 'api' ? 'badge-info' : 'badge'}">${escapeHtml(e.category)}</span></td>
<td style="font-size:13px"><strong>${escapeHtml(e.source)}</strong></td>
<td style="font-size:13px">${escapeHtml(e.message)}</td>
<td style="font-size:11px;color:var(--text-muted);font-family:var(--font-mono)">${e.id || ''}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<div style="font-size:12px;color:var(--text-muted);text-align:right;margin-top:8px">${errors.length} error${errors.length !== 1 ? 's' : ''}</div>
`;
} catch (err) {
if (container) container.innerHTML = `<div class="empty-state"><div class="empty-state-icon">⚠</div><div class="empty-state-title">${escapeHtml(err.message)}</div></div>`;
}
}
async function clearAllErrors() {
if (!confirm('Clear all error logs?')) return;
try {
await api.clearErrors();
showToast('Error log cleared', 'success');
refreshErrors();
} catch (err) {
showToast('Error: ' + err.message, 'error');
}
}
async function loadCircuitBreaker() {
const container = document.getElementById('circuitBreakerCards');
if (!container) return;
try {
const data = await api.getCircuitBreaker();
const agents = data.agents || {};
const agentNames = Object.keys(agents);
if (agentNames.length === 0) {
container.innerHTML = '<div style="grid-column:1/-1"><div class="empty-state" style="padding:20px"><div class="empty-state-icon">🔌</div><div class="empty-state-title">No circuit breaker data</div></div></div>';
return;
}
const agentIcons = { opencode: '🔧', hermes: '⚡', agy: '🧠' };
container.innerHTML = agentNames.map(a => {
const cb = agents[a] || {};
const isOpen = cb.state === 'open';
return `
<div class="card" style="border-color:${isOpen ? 'var(--red)' : 'var(--green-dim)'}">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
<span style="font-size:20px">${agentIcons[a] || '🤖'}</span>
<div>
<div style="font-weight:600;text-transform:capitalize">${escapeHtml(a)}</div>
<div style="font-size:12px;color:${isOpen ? 'var(--red)' : 'var(--green)'}">${cb.state || 'closed'}</div>
</div>
</div>
<div style="display:flex;gap:8px;font-size:12px;color:var(--text-muted);margin-bottom:8px">
<span>Failures: ${cb.failures || 0}</span>
<span>Threshold: ${data.threshold || 3}</span>
</div>
${isOpen ? `<button class="btn btn-sm btn-primary" onclick="resetCircuit('${a}')">🔓 Reset</button>` : ''}
</div>
`;
}).join('');
} catch {
container.innerHTML = '<div style="grid-column:1/-1"><div class="empty-state" style="padding:20px"><div class="empty-state-icon">⚠</div><div class="empty-state-title">Failed to load circuit breaker</div></div></div>';
}
}
async function resetCircuit(agent) {
try {
await api.resetCircuitBreaker(agent);
showToast(`Circuit breaker reset for ${agent}`, 'success');
loadCircuitBreaker();
} catch (err) {
showToast('Error: ' + err.message, 'error');
}
}