feat: fully SSE-driven detail/home pages, kill polling intervals
This commit is contained in:
parent
037013b564
commit
923f0349dc
|
|
@ -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);
|
||||||
|
document.getElementById('stat-status').innerHTML = ping.up
|
||||||
|
? '<span class="text-green-400">Up</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');
|
const tbody = document.getElementById('pings-table');
|
||||||
if (tbody) {
|
if (tbody) {
|
||||||
const upBadge = ping.up
|
|
||||||
? '<span class="text-green-400">Up</span>'
|
|
||||||
: '<span class="text-red-400">Down</span>';
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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`;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue