feat: fully SSE-driven detail/home pages, kill polling intervals

This commit is contained in:
M1 2026-03-16 17:10:12 +04:00
parent 037013b564
commit 923f0349dc
2 changed files with 65 additions and 13 deletions

View File

@ -221,6 +221,11 @@
const avgLatency = latencies.length ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null; const avgLatency = latencies.length ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null;
const uptime = results.length ? Math.round((upPings.length / results.length) * 100) : null; const uptime = results.length ? Math.round((upPings.length / results.length) * 100) : null;
// Seed running stats for SSE incremental updates
_totalPings = results.length;
_upPings = upPings.length;
_latencies = latencies.slice(-100);
document.getElementById('stat-status').innerHTML = lastPing document.getElementById('stat-status').innerHTML = lastPing
? (lastPing.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>') ? (lastPing.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>')
: '<span class="text-gray-500">—</span>'; : '<span class="text-gray-500">—</span>';
@ -353,32 +358,64 @@
}); });
load(); load();
setInterval(load, 60000); // fallback poll, less frequent now that SSE handles updates // No interval poll — SSE handles all live updates
// Track running stats in memory for incremental updates
let _totalPings = 0, _upPings = 0, _latencies = [];
// SSE: live ping updates // SSE: live ping updates
const id = window.location.pathname.split('/').pop(); const id = window.location.pathname.split('/').pop();
watchMonitor(id, (ping) => { watchMonitor(id, (ping) => {
// Update stat bar // ── Stats ───────────────────────────────────────────────────
document.getElementById('stat-last').innerHTML = timeAgo(ping.checked_at); _totalPings++;
if (ping.latency_ms) document.getElementById('stat-latency').textContent = ping.latency_ms + 'ms'; if (ping.up) _upPings++;
if (ping.latency_ms != null) {
_latencies.push(ping.latency_ms);
if (_latencies.length > 100) _latencies.shift();
const avg = Math.round(_latencies.reduce((a,b)=>a+b,0) / _latencies.length);
document.getElementById('stat-latency').textContent = avg + 'ms';
}
// Prepend new row to ping table document.getElementById('stat-last').innerHTML = timeAgo(ping.checked_at);
const tbody = document.getElementById('pings-table'); document.getElementById('stat-status').innerHTML = ping.up
if (tbody) {
const upBadge = ping.up
? '<span class="text-green-400">Up</span>' ? '<span class="text-green-400">Up</span>'
: '<span class="text-red-400">Down</span>'; : '<span class="text-red-400">Down</span>';
if (_totalPings > 0) {
const pct = Math.round((_upPings / _totalPings) * 100);
document.getElementById('stat-uptime').textContent = pct + '%';
}
// ── Status history bar — prepend a segment ───────────────────
const bar = document.getElementById('status-bar');
if (bar) {
const seg = document.createElement('div');
seg.className = `flex-1 ${ping.up ? 'bg-green-500/70' : 'bg-red-500/70'}`;
seg.title = `${new Date(ping.checked_at).toLocaleString()} — ${ping.up ? 'Up' : 'Down'}${ping.latency_ms ? ' ' + ping.latency_ms + 'ms' : ''}`;
bar.prepend(seg);
while (bar.children.length > 60) bar.removeChild(bar.lastChild);
}
// ── Latency chart ─────────────────────────────────────────────
if (ping.latency_ms != null) {
renderLatencyChart([...(_latencies.map((ms, i) => ({
latency_ms: ms, checked_at: new Date(Date.now() - (_latencies.length - i) * 10000).toISOString()
})))]);
}
// ── Ping table ───────────────────────────────────────────────
const tbody = document.getElementById('pings-table');
if (tbody) {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.className = 'hover:bg-gray-800/50'; tr.className = 'hover:bg-gray-800/50';
tr.innerHTML = ` tr.innerHTML = `
<td class="px-4 py-2">${upBadge}</td> <td class="px-4 py-2">${ping.up ? '<span class="text-green-400">Up</span>' : '<span class="text-red-400">Down</span>'}</td>
<td class="px-4 py-2 text-gray-300">${ping.status_code ?? '—'}</td> <td class="px-4 py-2 text-gray-300">${ping.status_code ?? '—'}</td>
<td class="px-4 py-2 text-gray-300">${ping.latency_ms ? ping.latency_ms + 'ms' : '—'}</td> <td class="px-4 py-2 text-gray-300">${ping.latency_ms ? ping.latency_ms + 'ms' : '—'}</td>
<td class="px-4 py-2 text-gray-500">${timeAgo(ping.checked_at)}</td> <td class="px-4 py-2 text-gray-500">${timeAgo(ping.checked_at)}</td>
<td class="px-4 py-2 text-red-400 text-xs">${ping.error ?? ''}</td> <td class="px-4 py-2 text-red-400/70 text-xs truncate max-w-[200px]">${ping.error ? escapeHtml(ping.error) : ''}</td>
`; `;
tbody.prepend(tr); tbody.prepend(tr);
// Trim to keep table manageable
while (tbody.children.length > 100) tbody.removeChild(tbody.lastChild); while (tbody.children.length > 100) tbody.removeChild(tbody.lastChild);
} }
}); });

View File

@ -92,7 +92,11 @@
} }
load(); load();
setInterval(load, 30000); // No interval poll — SSE handles live updates. Only reload on tab focus if stale.
let lastLoad = Date.now();
document.addEventListener('visibilitychange', () => {
if (!document.hidden && Date.now() - lastLoad > 60000) { load(); lastLoad = Date.now(); }
});
// SSE: subscribe to all monitors after load so cards update in real time // SSE: subscribe to all monitors after load so cards update in real time
const sseConnections = []; const sseConnections = [];
@ -107,7 +111,18 @@
// Status dot // Status dot
const statusDot = card.querySelector('.status-dot'); const statusDot = card.querySelector('.status-dot');
if (statusDot) statusDot.className = `status-dot w-2.5 h-2.5 rounded-full ${ping.up ? 'bg-green-500' : 'bg-red-500'}`; if (statusDot) {
const wasUp = statusDot.classList.contains('bg-green-500');
statusDot.className = `status-dot w-2.5 h-2.5 rounded-full ${ping.up ? 'bg-green-500' : 'bg-red-500'}`;
// If status changed, recount and update summary
if (wasUp !== ping.up) {
const dots = document.querySelectorAll('.status-dot');
const upNow = [...dots].filter(d => d.classList.contains('bg-green-500')).length;
const downNow = [...dots].filter(d => d.classList.contains('bg-red-500')).length;
const summary = document.getElementById('summary');
if (summary) summary.innerHTML = `<span class="text-green-400">${upNow} up</span> · <span class="${downNow > 0 ? 'text-red-400' : 'text-gray-500'}">${downNow} down</span> · ${dots.length} total`;
}
}
// Latency text // Latency text
if (ping.latency_ms) card.querySelector('.stat-latency').textContent = `${ping.latency_ms}ms`; if (ping.latency_ms) card.querySelector('.stat-latency').textContent = `${ping.latency_ms}ms`;