update: fixes, add rss link

This commit is contained in:
nate 2026-04-08 16:58:00 +04:00
parent 697513a5cf
commit 8feffa98bc
3 changed files with 66 additions and 28 deletions

View File

@ -58,6 +58,56 @@ export interface MonitorRow {
latency_history: Array<{ region: string; latency_ms: number | null; ts: string }>;
}
// Average latency of the *fastest* region per monitor over a given window.
// Status pages are customer-facing — we want to show our best foot forward,
// not a noisy average that gets dragged down by a single distant region.
export async function loadFastestRegionLatency(
monitorIds: string[],
bucket: BucketType,
intervalLiteral: string,
): Promise<Record<string, number | null>> {
const out: Record<string, number | null> = {};
if (monitorIds.length === 0) return out;
for (const id of monitorIds) out[id] = null;
const ids = sql.array(monitorIds);
let rows = await sql<any[]>`
SELECT monitor_id, region,
(sum(avg_latency * total) / NULLIF(sum(total), 0))::float AS avg_lat
FROM monitor_uptime_rollup
WHERE monitor_id = ANY(${ids}::text[])
AND bucket_type = ${bucket}
AND bucket_start > now() - ${intervalLiteral}::interval
AND avg_latency IS NOT NULL
GROUP BY 1, 2
`;
if (rows.length === 0) {
// Fallback while rollup is unpopulated. Bounded by the same window so cheap.
rows = await sql<any[]>`
SELECT monitor_id, COALESCE(region, 'default') AS region,
avg(latency_ms)::float AS avg_lat
FROM pings
WHERE monitor_id = ANY(${ids}::text[])
AND checked_at > now() - ${intervalLiteral}::interval
AND latency_ms IS NOT NULL
GROUP BY 1, 2
`;
}
// For each monitor, keep the region with the lowest average latency.
for (const r of rows) {
if (r.avg_lat == null) continue;
const cur = out[r.monitor_id];
if (cur == null || r.avg_lat < cur) out[r.monitor_id] = r.avg_lat;
}
// Round to integer ms.
for (const id of Object.keys(out)) {
if (out[id] != null) out[id] = Math.round(out[id] as number);
}
return out;
}
// Single SQL pass that produces all four uptime windows for a set of monitors.
// Reads only the rollup table; falls back to a pings aggregate when the rollup
// has nothing for these monitors yet (same pattern as loadMonitors).
@ -226,18 +276,14 @@ export async function loadMonitors(pageId: string, window: Window, pageDisplayMo
// Index actual rollup data by (monitor_id, isoBucketStart) so we can fill in
// the missing slots below.
const indexed: Record<string, Record<string, { total: number; up: number; avg_latency: number | null }>> = {};
const latencyByMonitor: Record<string, { sum: number; n: number }> = {};
for (const r of rollupRows) {
const startIso = r.bucket_start instanceof Date ? r.bucket_start.toISOString() : String(r.bucket_start);
if (!indexed[r.monitor_id]) indexed[r.monitor_id] = {};
indexed[r.monitor_id]![startIso] = { total: r.total, up: r.up_count, avg_latency: r.avg_latency ?? null };
if (r.avg_latency != null) {
const acc = latencyByMonitor[r.monitor_id] ?? { sum: 0, n: 0 };
acc.sum += r.avg_latency * r.total;
acc.n += r.total;
latencyByMonitor[r.monitor_id] = acc;
}
}
// Customer-facing latency = average of the fastest region for the page's
// window. Computed via a separate query that retains per-region info.
const fastestLatency = await loadFastestRegionLatency(ids, bucket, intervalLiteral);
// Generate the full sequence of expected bucket timestamps so empty bars
// render as "no data" instead of disappearing entirely. Truncate `now()` to
@ -305,8 +351,7 @@ export async function loadMonitors(pageId: string, window: Window, pageDisplayMo
const upT = buckets.reduce((a, b) => a + b.up, 0);
uptime_pct = tot > 0 ? +(100 * upT / tot).toFixed(2) : null;
}
const latAcc = latencyByMonitor[m.id];
const avg_latency = latAcc && latAcc.n > 0 ? Math.round(latAcc.sum / latAcc.n) : null;
const avg_latency = fastestLatency[m.id] ?? null;
// Per-monitor display mode override → page default → 'expanded'.
const display_mode = (m.spm_display_mode === 'compact' || m.spm_display_mode === 'expanded')
? m.spm_display_mode

View File

@ -88,16 +88,6 @@
var barsHtml = renderBars(buckets);
var regionsHtml = "";
if (m.region_states && m.region_states.length > 1) {
regionsHtml = '<div class="regions">';
for (var r = 0; r < m.region_states.length; r++) {
var rs = m.region_states[r];
regionsHtml += '<span class="region ' + rs.state + '">' + escapeHtml(rs.region) + "</span>";
}
regionsHtml += "</div>";
}
var latencyHtml = "";
if (showResponseTime && m.avg_latency != null) {
latencyHtml = "<span>" + m.avg_latency + "ms · </span>";
@ -119,7 +109,6 @@
html += '<div class="uptime-cell ' + uptimeBand(u.d30) + '"><div class="label">30d</div><div class="value">' + fmtUptime(u.d30) + "</div></div>";
html += '<div class="uptime-cell ' + uptimeBand(u.d90) + '"><div class="label">90d</div><div class="value">' + fmtUptime(u.d90) + "</div></div>";
html += "</div>";
html += regionsHtml;
html += incidentsHtml;
return html;
}

View File

@ -101,6 +101,10 @@
body { margin: 0; background: var(--bg); color: var(--fg); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif; line-height: 1.5; }
main { max-width: 880px; margin: 0 auto; padding: 3rem 1.5rem; }
h1 { font-size: 1.75rem; font-weight: 700; margin: 0 0 0.5rem; }
.title-row { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
.title-row h1 { margin: 0; }
.rss-link { color: var(--muted); display: inline-flex; align-items: center; transition: color 0.15s; }
.rss-link:hover { color: #f59e0b; }
.muted { color: var(--muted); font-size: 0.875rem; }
.overall { padding: 1.25rem 1.5rem; border-radius: 12px; color: var(--overall-fg); font-weight: 600; font-size: 1.05rem; margin: 1.5rem 0 2rem; display: flex; align-items: center; gap: 0.75rem; }
.overall .dot { width: 12px; height: 12px; border-radius: 50%; background: var(--overall-fg); opacity: 0.9; }
@ -177,7 +181,14 @@
</head>
<body>
<main>
<h1><%= page.title %></h1>
<div class="title-row">
<h1><%= page.title %></h1>
<a class="rss-link" href="/<%= page.slug %>.rss" title="Subscribe via RSS" aria-label="Subscribe via RSS">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M6.18 17.82a2.18 2.18 0 1 1-4.36 0 2.18 2.18 0 0 1 4.36 0zM4 4.44v3.43c7.78 0 14.13 6.35 14.13 14.13h3.43C21.56 12.4 13.6 4.44 4 4.44zM4 10.96v3.43a7.52 7.52 0 0 1 7.55 7.61h3.43A10.94 10.94 0 0 0 4 10.96z"/>
</svg>
</a>
</div>
<% if (page.description) { %><div class="muted"><%= page.description %></div><% } %>
<div class="overall" style="background: <%= overallColor %>;">
@ -284,13 +295,6 @@
<div class="uptime-cell <%= uptimeBand(u.d30) %>"><div class="label">30d</div><div class="value"><%= fmtUptime(u.d30) %></div></div>
<div class="uptime-cell <%= uptimeBand(u.d90) %>"><div class="label">90d</div><div class="value"><%= fmtUptime(u.d90) %></div></div>
</div>
<% if (m.region_states && m.region_states.length > 1) { %>
<div class="regions">
<% m.region_states.forEach(function(r) { %>
<span class="region <%= r.state %>"><%= r.region %></span>
<% }); %>
</div>
<% } %>
</div>
<% } %>
<% }); %>