diff --git a/apps/web/src/utils/sparkline.ts b/apps/web/src/utils/sparkline.ts
index c5d4cb5..6aa076c 100644
--- a/apps/web/src/utils/sparkline.ts
+++ b/apps/web/src/utils/sparkline.ts
@@ -9,5 +9,5 @@ export function sparkline(values: number[], width = 120, height = 32): string {
const y = height - ((v - min) / range) * (height - 4) - 2;
return `${x},${y}`;
}).join(' ');
- return ``;
+ return ``;
}
diff --git a/apps/web/src/views/home.ejs b/apps/web/src/views/home.ejs
index 32303cc..df9ee3b 100644
--- a/apps/web/src/views/home.ejs
+++ b/apps/web/src/views/home.ejs
@@ -75,9 +75,8 @@
} catch {}
}, 30000);
- // SSE: on each ping, update text fields and fetch fresh sparkline
- const _fetchingSparkline = new Set();
- watchAccount(async (ping) => {
+ // SSE: on each ping, update text fields and redraw sparkline in place
+ watchAccount((ping) => {
const card = document.querySelector(`[data-monitor-id="${ping.monitor_id}"]`);
if (!card) return;
@@ -89,22 +88,31 @@
if (ping.latency_ms != null) card.querySelector('.stat-latency').textContent = ping.latency_ms + 'ms';
card.querySelector('.stat-last').innerHTML = timeAgo(ping.checked_at);
- // Sparkline
- const sparkEl = card.querySelector('.stat-sparkline');
- if (!sparkEl || _fetchingSparkline.has(ping.monitor_id)) return;
- _fetchingSparkline.add(ping.monitor_id);
- try {
- const res = await fetch(`/dashboard/monitors/${ping.monitor_id}/sparkline`, { credentials: 'same-origin' });
- if (res.ok) {
- const tmp = document.createElement('div');
- tmp.innerHTML = await res.text();
- const newSvg = tmp.firstElementChild;
- const oldSvg = sparkEl.querySelector('svg');
- if (newSvg && oldSvg) oldSvg.replaceWith(newSvg);
- else if (newSvg) sparkEl.appendChild(newSvg);
+ // Sparkline — append point to existing polyline, drop oldest, no refetch
+ if (ping.latency_ms != null) {
+ const sparkEl = card.querySelector('.stat-sparkline');
+ const polyline = sparkEl?.querySelector('polyline');
+ if (polyline) {
+ const pts = polyline.getAttribute('points').trim().split(' ').filter(Boolean);
+ const W = 120, H = 32;
+ // Parse existing points
+ let coords = pts.map(p => p.split(',').map(Number));
+ // Extract just y-values (latencies are encoded in y relative to scale)
+ // Easier: maintain a data attr on the svg with the raw values
+ const svg = sparkEl.querySelector('svg');
+ let vals = svg?.dataset.vals ? svg.dataset.vals.split(',').map(Number) : [];
+ vals.push(ping.latency_ms);
+ if (vals.length > 20) vals.shift();
+ if (svg) svg.dataset.vals = vals.join(',');
+ // Redraw polyline in place
+ const max = Math.max(...vals, 1);
+ const min = Math.min(...vals, 0);
+ const range = max - min || 1;
+ const step = W / Math.max(vals.length - 1, 1);
+ const newPts = vals.map((v, i) => `${i * step},${H - ((v - min) / range) * (H - 4) - 2}`).join(' ');
+ polyline.setAttribute('points', newPts);
}
- } catch {}
- _fetchingSparkline.delete(ping.monitor_id);
+ }
});