diff --git a/apps/status/src/static/app.css b/apps/status/src/static/app.css index 0d6667a..ad05f78 100644 --- a/apps/status/src/static/app.css +++ b/apps/status/src/static/app.css @@ -52,7 +52,11 @@ h1 { font-size: 1.75rem; font-weight: 700; margin: 0 0 0.5rem; } .group-chev { color: var(--muted); transition: transform 0.15s; flex-shrink: 0; } .group-toggle:checked ~ .group-header .group-chev { transform: rotate(90deg); } .group-name { font-size: 0.85rem; font-weight: 600; color: var(--fg); } +.group-header-right { display: flex; align-items: center; gap: 1rem; } .group-status { font-size: 0.75rem; font-weight: 600; } +.group-aggregate { padding: 0 1.25rem 0.75rem; } +.group-aggregate .bars { height: 24px; } +.group-toggle:checked ~ .group-aggregate { display: none; } .group-body { display: none; padding: 0 0.75rem 0.75rem; } .group-toggle:checked ~ .group-body { display: block; } .group-body .monitors { gap: 0.35rem; } diff --git a/apps/status/src/views/page.ejs b/apps/status/src/views/page.ejs index 6a9c55b..8184b8d 100644 --- a/apps/status/src/views/page.ejs +++ b/apps/status/src/views/page.ejs @@ -192,16 +192,45 @@ const groupStatusLabel = groupStatus === 'up' ? 'Operational' : groupStatus === 'degraded' ? 'Degraded' : groupStatus === 'down' ? 'Down' : ''; const groupStatusColor = groupStatus === 'up' ? 'var(--bar-up)' : groupStatus === 'degraded' ? 'var(--bar-partial)' : groupStatus === 'down' ? 'var(--bar-down)' : 'var(--muted)'; %> - <% if (groupName) { %> + <% if (groupName) { + // Aggregate buckets across all monitors in the group (weighted average) + const aggBuckets = []; + const firstWithBuckets = list.find(m => m.buckets && m.buckets.length > 0); + if (firstWithBuckets) { + for (let bi = 0; bi < firstWithBuckets.buckets.length; bi++) { + let t = 0, u = 0, latSum = 0, latN = 0; + for (const m of list) { + if (!m.buckets || !m.buckets[bi]) continue; + t += m.buckets[bi].total; + u += m.buckets[bi].up; + if (m.buckets[bi].avg_latency != null) { latSum += m.buckets[bi].avg_latency * m.buckets[bi].total; latN += m.buckets[bi].total; } + } + aggBuckets.push({ start: firstWithBuckets.buckets[bi].start, total: t, up: u, avg_latency: latN > 0 ? Math.round(latSum / latN) : null }); + } + } + const aggTotal = aggBuckets.reduce((a, b) => a + b.total, 0); + const aggUp = aggBuckets.reduce((a, b) => a + b.up, 0); + const aggPct = aggTotal > 0 ? (100 * aggUp / aggTotal) : null; + %>