Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
91 lines
4.1 KiB
JavaScript
91 lines
4.1 KiB
JavaScript
/**
|
|
* Admin > Monitoring > Dashboard
|
|
*/
|
|
const { html } = window;
|
|
const { useState, useEffect, useCallback, useRef } = hooks;
|
|
|
|
export default function DashboardSection() {
|
|
const [data, setData] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const timerRef = useRef(null);
|
|
|
|
const load = useCallback(async () => {
|
|
try {
|
|
const d = await sw.api.admin.dashboard();
|
|
setData(d);
|
|
} catch (e) { sw.toast(e.message, 'error'); }
|
|
finally { setLoading(false); }
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
timerRef.current = setInterval(load, 10000);
|
|
return () => clearInterval(timerRef.current);
|
|
}, []);
|
|
|
|
function formatUptime(seconds) {
|
|
if (!seconds) return '\u2014';
|
|
const d = Math.floor(seconds / 86400);
|
|
const h = Math.floor((seconds % 86400) / 3600);
|
|
const m = Math.floor((seconds % 3600) / 60);
|
|
if (d > 0) return `${d}d ${h}h`;
|
|
if (h > 0) return `${h}h ${m}m`;
|
|
return `${m}m`;
|
|
}
|
|
|
|
function formatBytes(b) {
|
|
if (!b) return '0 B';
|
|
if (b < 1048576) return (b / 1024).toFixed(0) + ' KB';
|
|
if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB';
|
|
return (b / 1073741824).toFixed(2) + ' GB';
|
|
}
|
|
|
|
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
|
|
if (!data) return html`<div class="empty-hint">No dashboard data</div>`;
|
|
|
|
return html`
|
|
<div>
|
|
<p class="text-muted" style="font-size:12px;margin-bottom:12px;">Auto-refreshes every 10s</p>
|
|
|
|
<div class="stat-cards-grid" style="margin-bottom:16px;">
|
|
<div class="stat-card"><div class="stat-value">${formatUptime(data.uptime_seconds)}</div><div class="stat-label">Uptime</div></div>
|
|
<div class="stat-card"><div class="stat-value">${data.active_connections ?? '\u2014'}</div><div class="stat-label">Connections</div></div>
|
|
<div class="stat-card"><div class="stat-value">${data.goroutines ?? '\u2014'}</div><div class="stat-label">Goroutines</div></div>
|
|
<div class="stat-card"><div class="stat-value">${formatBytes(data.heap_alloc)}</div><div class="stat-label">Heap</div></div>
|
|
</div>
|
|
|
|
${data.db_pool && html`
|
|
<div class="stat-cards-grid" style="margin-bottom:16px;">
|
|
<div class="stat-card"><div class="stat-value">${data.db_pool.open || 0}</div><div class="stat-label">DB Open</div></div>
|
|
<div class="stat-card"><div class="stat-value">${data.db_pool.in_use || 0}</div><div class="stat-label">DB In Use</div></div>
|
|
<div class="stat-card"><div class="stat-value">${data.db_pool.idle || 0}</div><div class="stat-label">DB Idle</div></div>
|
|
</div>
|
|
`}
|
|
|
|
${data.providers && data.providers.length > 0 && html`
|
|
<h4 style="margin:16px 0 8px;">Provider Health</h4>
|
|
<div class="stat-cards-grid">
|
|
${data.providers.map(p => html`
|
|
<div class="dashboard-provider-card" key=${p.name || p.id}>
|
|
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
|
|
<strong style="font-size:13px;">${p.name || p.id}</strong>
|
|
<span class="badge ${p.status === 'healthy' ? 'badge-active' : p.status === 'degraded' ? 'badge-pending' : 'badge-inactive'}">${p.status}</span>
|
|
</div>
|
|
<div style="font-size:11px;color:var(--text-3);">${Math.round(p.avg_latency_ms || 0)}ms avg</div>
|
|
</div>
|
|
`)}
|
|
</div>
|
|
`}
|
|
|
|
${data.recent_errors && data.recent_errors.length > 0 && html`
|
|
<h4 style="margin:16px 0 8px;">Recent Errors</h4>
|
|
<div class="admin-list">
|
|
${data.recent_errors.map((e, i) => html`
|
|
<div class="admin-surface-row" key=${i} style="font-size:12px;color:var(--danger);">${e}</div>
|
|
`)}
|
|
</div>
|
|
`}
|
|
</div>
|
|
`;
|
|
}
|