update: fixes, add rss link
This commit is contained in:
parent
697513a5cf
commit
8feffa98bc
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
|
|
|
|||
Loading…
Reference in New Issue