diff --git a/apps/web/src/views/detail.ejs b/apps/web/src/views/detail.ejs
index f5eb334..481bd81 100644
--- a/apps/web/src/views/detail.ejs
+++ b/apps/web/src/views/detail.ejs
@@ -9,7 +9,19 @@
const latencies = pings.filter(p => p.latency_ms != null).map(p => p.latency_ms);
const avgLatency = latencies.length ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null;
const uptime = pings.length ? Math.round((upPings.length / pings.length) * 100) : null;
- const barPings = pings.slice(0, 60).reverse();
+ // Group pings by run_id for status bar
+ const barRuns = [];
+ const runMap = {};
+ for (const p of pings.slice(0, 120).reverse()) {
+ const rid = p.run_id || p.checked_at;
+ if (!runMap[rid]) {
+ runMap[rid] = { run_id: rid, up: 0, down: 0, checked_at: p.checked_at, latency_ms: p.latency_ms };
+ barRuns.push(runMap[rid]);
+ }
+ if (p.up) runMap[rid].up++; else runMap[rid].down++;
+ }
+ // Keep last 60 runs
+ const barPings = barRuns.slice(-60);
const chartPings = pings.slice().reverse();
%>
@@ -80,8 +92,11 @@
Status History
<% if (barPings.length > 0) { %>
- <% barPings.forEach(function(c) { %>
-
+ <% barPings.forEach(function(c) {
+ const color = c.down === 0 ? 'bg-green-500/70' : (c.up === 0 ? 'bg-red-500/70' : 'bg-orange-400/70');
+ const label = c.down === 0 ? 'Up' : (c.up === 0 ? 'Down' : 'Partial');
+ %>
+
<% }) %>
<% } else { %>
No data
@@ -479,14 +494,31 @@
// Last ping
document.getElementById('stat-last').innerHTML = timeAgo(ping.checked_at);
- // Status bar — prepend segment, cap at 60
+ // Status bar — group by run_id, cap at 60
const bar = document.getElementById('status-bar');
if (bar) {
- const seg = document.createElement('div');
- seg.className = `flex-1 rounded-sm ${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);
+ const rid = ping.run_id || ping.checked_at;
+ let existing = bar.querySelector(`[data-run="${rid}"]`);
+ if (existing) {
+ // Update existing run segment
+ const up = parseInt(existing.dataset.up || '0') + (ping.up ? 1 : 0);
+ const down = parseInt(existing.dataset.down || '0') + (ping.up ? 0 : 1);
+ existing.dataset.up = up;
+ existing.dataset.down = down;
+ const color = down === 0 ? 'bg-green-500/70' : (up === 0 ? 'bg-red-500/70' : 'bg-orange-400/70');
+ const label = down === 0 ? 'Up' : (up === 0 ? 'Down' : 'Partial');
+ existing.className = `flex-1 rounded-sm ${color}`;
+ existing.title = `${new Date(ping.checked_at).toLocaleString()} — ${label}`;
+ } else {
+ const seg = document.createElement('div');
+ seg.className = `flex-1 rounded-sm ${ping.up ? 'bg-green-500/70' : 'bg-red-500/70'}`;
+ seg.dataset.run = rid;
+ seg.dataset.up = ping.up ? '1' : '0';
+ seg.dataset.down = ping.up ? '0' : '1';
+ seg.title = `${new Date(ping.checked_at).toLocaleString()} — ${ping.up ? 'Up' : 'Down'}`;
+ bar.appendChild(seg);
+ while (bar.children.length > 60) bar.removeChild(bar.firstChild);
+ }
}
// Pings table — prepend row, cap at 100