fix: SSE-driven chart/sparkline refresh, debounced server-side partials

This commit is contained in:
M1 2026-03-16 21:21:56 +04:00
parent 2f7273604b
commit 5071e340c7
3 changed files with 54 additions and 11 deletions

View File

@ -196,5 +196,26 @@ export const dashboard = new Elysia()
}); });
}) })
// Sparkline partial — returns just the SVG for one monitor
.get("/dashboard/monitors/:id/sparkline", async ({ cookie, headers, params }) => {
const accountId = await getAccountId(cookie, headers);
if (!accountId) return new Response("Unauthorized", { status: 401 });
const [monitor] = await sql`
SELECT id FROM monitors WHERE id = ${params.id} AND account_id = ${accountId}
`;
if (!monitor) return new Response("Not found", { status: 404 });
const pings = await sql`
SELECT latency_ms FROM pings
WHERE monitor_id = ${params.id} AND latency_ms IS NOT NULL
ORDER BY checked_at DESC LIMIT 20
`;
const latencies = pings.map((p: any) => p.latency_ms).reverse();
return new Response(sparklineSSR(latencies), {
headers: { "content-type": "text/html; charset=utf-8" },
});
})
// Docs // Docs
.get("/docs", () => Bun.file(`${dashDir}/docs.html`)); .get("/docs", () => Bun.file(`${dashDir}/docs.html`));

View File

@ -304,6 +304,9 @@
while (bar.children.length > 60) bar.removeChild(bar.lastChild); while (bar.children.length > 60) bar.removeChild(bar.lastChild);
} }
// Refresh latency chart (debounced)
scheduleChartRefresh();
// Ping table — prepend plain HTML row // Ping table — prepend plain HTML row
const tbody = document.getElementById('pings-table'); const tbody = document.getElementById('pings-table');
if (tbody) { if (tbody) {
@ -321,16 +324,18 @@
} }
}); });
// Poll every 30s to refresh the latency chart from server // Refresh latency chart from server (debounced — at most once per 5s)
setInterval(async () => { let _chartRefreshTimer = null;
try { function scheduleChartRefresh() {
const res = await fetch(`/dashboard/monitors/${monitorId}/chart`, { credentials: 'same-origin' }); if (_chartRefreshTimer) return;
if (res.ok) { _chartRefreshTimer = setTimeout(async () => {
const html = await res.text(); _chartRefreshTimer = null;
document.getElementById('latency-chart').innerHTML = html; try {
} const res = await fetch(`/dashboard/monitors/${monitorId}/chart`, { credentials: 'same-origin' });
} catch {} if (res.ok) document.getElementById('latency-chart').innerHTML = await res.text();
}, 30000); } catch {}
}, 5000);
}
</script> </script>
<%~ include('./partials/foot') %> <%~ include('./partials/foot') %>

View File

@ -75,7 +75,20 @@
} catch {} } catch {}
}, 30000); }, 30000);
// SSE: subscribe to all monitors for live updates (minimal DOM patches only) // Sparkline refresh — debounced per monitor (at most once per 5s)
const _sparklineTimers = {};
function scheduleSparklineRefresh(mid, sparkEl) {
if (_sparklineTimers[mid]) return;
_sparklineTimers[mid] = setTimeout(async () => {
delete _sparklineTimers[mid];
try {
const res = await fetch(`/dashboard/monitors/${mid}/sparkline`, { credentials: 'same-origin' });
if (res.ok) sparkEl.innerHTML = await res.text();
} catch {}
}, 5000);
}
// SSE: subscribe to all monitors for live updates
const sseConnections = []; const sseConnections = [];
function subscribeAll() { function subscribeAll() {
const monitorCards = document.querySelectorAll('[data-monitor-id]'); const monitorCards = document.querySelectorAll('[data-monitor-id]');
@ -101,6 +114,10 @@
// Timestamp // Timestamp
card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at); card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at);
// Sparkline — debounced fetch from server
const sparkEl = card.querySelector('.stat-sparkline');
if (sparkEl) scheduleSparklineRefresh(mid, sparkEl);
}); });
if (es) sseConnections.push(es); if (es) sseConnections.push(es);
}); });