feat: Updated monitor chart
Chart Release / chart (push) Successful in 15s
Server Deploy / deploy (push) Successful in 1m9s

This commit is contained in:
2026-08-25 15:02:04 +00:00
parent c440b59b93
commit 049e005873
4 changed files with 86 additions and 20 deletions
+9 -3
View File
@@ -16,7 +16,9 @@ import {
formatDuration,
formatMs,
formatPct,
markIncidents,
relativeTime,
slotChartColor,
slotLabel,
statusStripe,
targetSummary,
@@ -206,7 +208,7 @@ function History({ slots, note }: { slots: Slot[]; note?: string }) {
<span
className={`w-full rounded-[1px] transition-opacity ${
hovered !== null && hovered !== i ? "opacity-50" : ""
} ${s.pct === null ? "bg-border-soft" : s.pct >= 99.5 ? "bg-success/60" : s.pct >= 80 ? "bg-warning/70" : "bg-danger/80"}`}
} ${slotChartColor(s)}`}
style={{ height: s.pct === null ? "18%" : `${Math.max(s.pct, 12)}%` }}
/>
</button>
@@ -408,10 +410,14 @@ export default function MonitorDetailPage() {
}
const all: Rollup[] = rollups ?? [];
const slots =
/* Marked with the incidents, so a bar is red only where the monitor was
actually down — a failed check the retry policy absorbed stays amber. */
const slots = markIncidents(
range.source === "rollups"
? buildSlots(all, Math.round(range.minutes / 60))
: buildSampleSlots(samples ?? [], range.minutes * 60_000, range.bucketMs);
: buildSampleSlots(samples ?? [], range.minutes * 60_000, range.bucketMs),
incidents ?? [],
);
/* Samples expire after 48h and only start accruing once a check runs, so an
empty short range is a real answer and not a failure to load. */
const emptyRange = range.source === "samples" && (samples ?? []).length === 0;
+30 -11
View File
@@ -3,7 +3,7 @@
import { useCallback, useEffect, useState } from "react";
import { useQueries, useQuery } from "@tanstack/react-query";
import Link from "next/link";
import { api, Monitor, Rollup } from "@/lib/api";
import { api, Incident, Monitor, Rollup } from "@/lib/api";
import { Button } from "@/components/ui";
import {
DisplayStatus,
@@ -14,6 +14,7 @@ import {
displayStatus,
formatMs,
formatPct,
markIncidents,
relativeTime,
statusStripe,
targetSummary,
@@ -79,11 +80,17 @@ const UNGROUPED = "Ungrouped";
interface MonitorGroup {
name: string;
rows: { monitor: Monitor; rollups: Rollup[] }[];
rows: MonitorRowData[];
}
interface MonitorRowData {
monitor: Monitor;
rollups: Rollup[];
incidents: Incident[];
}
/** Alphabetical, with the ungrouped remainder last so it reads as a leftover. */
function groupMonitors(rows: { monitor: Monitor; rollups: Rollup[] }[]): MonitorGroup[] {
function groupMonitors(rows: MonitorRowData[]): MonitorGroup[] {
const byName = new Map<string, MonitorGroup["rows"]>();
for (const row of rows) {
const name = row.monitor.group?.trim() || UNGROUPED;
@@ -170,9 +177,11 @@ function GroupHeader({
);
}
function MonitorRow({ monitor, rollups }: { monitor: Monitor; rollups: Rollup[] }) {
function MonitorRow({ monitor, rollups, incidents }: MonitorRowData) {
const status = displayStatus(monitor);
const slots = buildSlots(rollups);
/* Incidents, not raw check results, decide which hours read as down — see
markIncidents. */
const slots = markIncidents(buildSlots(rollups), incidents);
const pct = uptimePct(rollups.slice(-24));
const latency = monitor.state.latency_ms > 0 ? monitor.state.latency_ms : avgLatency(rollups.slice(-1));
@@ -224,9 +233,21 @@ export default function MonitorsPage() {
})),
});
const incidentQueries = useQueries({
queries: (monitors ?? []).map((m) => ({
queryKey: ["monitors", m.monitor_id, "incidents"],
queryFn: () => api.getMonitorIncidents(m.monitor_id),
refetchInterval: 60_000,
})),
});
const { collapsed, toggle } = useCollapsedGroups();
const rows = (monitors ?? []).map((m, i) => ({ monitor: m, rollups: uptimeQueries[i]?.data ?? [] }));
const rows: MonitorRowData[] = (monitors ?? []).map((m, i) => ({
monitor: m,
rollups: uptimeQueries[i]?.data ?? [],
incidents: incidentQueries[i]?.data ?? [],
}));
const grouped = rows.some(({ monitor }) => !!monitor.group?.trim());
const groups = groupMonitors(rows);
@@ -292,17 +313,15 @@ export default function MonitorsPage() {
<div key={group.name} className="overflow-hidden rounded-lg border border-border bg-surface">
<GroupHeader group={group} collapsed={isCollapsed} onToggle={() => toggle(group.name)} />
{!isCollapsed &&
group.rows.map(({ monitor, rollups }) => (
<MonitorRow key={monitor.monitor_id} monitor={monitor} rollups={rollups} />
))}
group.rows.map((row) => <MonitorRow key={row.monitor.monitor_id} {...row} />)}
</div>
);
})}
</div>
) : (
<div className="overflow-hidden rounded-lg border border-border bg-surface">
{rows.map(({ monitor, rollups }) => (
<MonitorRow key={monitor.monitor_id} monitor={monitor} rollups={rollups} />
{rows.map((row) => (
<MonitorRow key={row.monitor.monitor_id} {...row} />
))}
</div>
)}