From 05efd174d3ef42cd10646a2da173e2defc592acd Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Sat, 14 Mar 2026 23:27:15 +0000 Subject: [PATCH 1/3] fix: use node current status as fallback instead of hardcoded UNKNOWN When a node has no status event history within the selected range, the timeline and uptime router now fall back to the node's actual current status from the DB rather than always showing "UNKNOWN". Co-Authored-By: Claude Sonnet 4.6 --- src/components/fleet/status-timeline.tsx | 8 +++--- src/server/routers/fleet.ts | 34 +++++++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/components/fleet/status-timeline.tsx b/src/components/fleet/status-timeline.tsx index 565b823..51a3d8b 100644 --- a/src/components/fleet/status-timeline.tsx +++ b/src/components/fleet/status-timeline.tsx @@ -55,10 +55,12 @@ function formatDuration(ms: number): string { export function StatusTimeline({ nodeId, range, onRangeChange }: StatusTimelineProps) { const trpc = useTRPC(); - const { data: events, isLoading, dataUpdatedAt } = useQuery({ + const { data, isLoading, dataUpdatedAt } = useQuery({ ...trpc.fleet.getStatusTimeline.queryOptions({ nodeId, range }), refetchInterval: 15_000, }); + const events = data?.events; + const nodeStatus = data?.nodeStatus ?? "UNKNOWN"; type Segment = { status: string; @@ -82,7 +84,7 @@ export function StatusTimeline({ nodeId, range, onRangeChange }: StatusTimelineP if (events !== undefined && now > 0) { if (events.length === 0) { - segs.push({ status: "UNKNOWN", start: rangeStart, end: now }); + segs.push({ status: nodeStatus, start: rangeStart, end: now }); } else { // First segment: from range start to first event const firstStatus = events[0].fromStatus ?? "UNKNOWN"; @@ -98,7 +100,7 @@ export function StatusTimeline({ nodeId, range, onRangeChange }: StatusTimelineP } return { segments: segs, totalMs: now - rangeStart }; - }, [events, range, dataUpdatedAt]); + }, [events, nodeStatus, range, dataUpdatedAt]); return (
diff --git a/src/server/routers/fleet.ts b/src/server/routers/fleet.ts index 10ce782..bc882c3 100644 --- a/src/server/routers/fleet.ts +++ b/src/server/routers/fleet.ts @@ -72,10 +72,17 @@ export const fleetRouter = router({ "30d": 30 * 24 * 60 * 60 * 1000, }; const since = new Date(Date.now() - rangeMs[input.range]); - return prisma.nodeStatusEvent.findMany({ - where: { nodeId: input.nodeId, timestamp: { gte: since } }, - orderBy: { timestamp: "asc" }, - }); + const [events, node] = await Promise.all([ + prisma.nodeStatusEvent.findMany({ + where: { nodeId: input.nodeId, timestamp: { gte: since } }, + orderBy: { timestamp: "asc" }, + }), + prisma.vectorNode.findUnique({ + where: { id: input.nodeId }, + select: { status: true }, + }), + ]); + return { events, nodeStatus: node?.status ?? "UNKNOWN" }; }), getUptime: protectedProcedure @@ -100,16 +107,23 @@ export const fleetRouter = router({ orderBy: { timestamp: "asc" }, }); - // Get the last event before the range to know starting status - const priorEvent = await prisma.nodeStatusEvent.findFirst({ - where: { nodeId: input.nodeId, timestamp: { lt: since } }, - orderBy: { timestamp: "desc" }, - }); + // Get the last event before the range to know starting status, + // and the node's current status as a fallback for nodes with no event history + const [priorEvent, nodeForStatus] = await Promise.all([ + prisma.nodeStatusEvent.findFirst({ + where: { nodeId: input.nodeId, timestamp: { lt: since } }, + orderBy: { timestamp: "desc" }, + }), + prisma.vectorNode.findUnique({ + where: { id: input.nodeId }, + select: { status: true }, + }), + ]); // Walk events, tracking time in HEALTHY status let healthySeconds = 0; let incidents = 0; - let currentStatus = priorEvent?.toStatus ?? "UNKNOWN"; + let currentStatus = priorEvent?.toStatus ?? nodeForStatus?.status ?? "UNKNOWN"; let cursor = since.getTime(); for (const event of events) { From 836d7e504b30436e5592b1cc130bd683b319a263 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Sun, 15 Mar 2026 15:51:05 +0000 Subject: [PATCH 2/3] fix: update EventLog to destructure new getStatusTimeline response shape getStatusTimeline now returns { events, nodeStatus } instead of a plain array. EventLog was still treating data as an array, causing it to always render empty. Destructure data.events to restore correct behaviour. Co-Authored-By: Claude Sonnet 4.6 --- src/components/fleet/event-log.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/fleet/event-log.tsx b/src/components/fleet/event-log.tsx index b225e06..b8fa89b 100644 --- a/src/components/fleet/event-log.tsx +++ b/src/components/fleet/event-log.tsx @@ -32,10 +32,11 @@ function formatTime(date: Date | string): string { export function EventLog({ nodeId, range }: EventLogProps) { const trpc = useTRPC(); - const { data: events, isLoading } = useQuery({ + const { data, isLoading } = useQuery({ ...trpc.fleet.getStatusTimeline.queryOptions({ nodeId, range }), refetchInterval: 15_000, }); + const events = data?.events; if (isLoading) { return ( From c7ae994a55e5ca1869be37a62eca672cfe7cc224 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Tue, 17 Mar 2026 15:23:58 +0000 Subject: [PATCH 3/3] perf: fetch uptime events and prior-event queries in parallel Collapses three sequential DB round-trips in getUptime into a single Promise.all, reducing latency when loading node health cards. Co-Authored-By: Claude Sonnet 4.6 --- src/server/routers/fleet.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/server/routers/fleet.ts b/src/server/routers/fleet.ts index bc882c3..19be542 100644 --- a/src/server/routers/fleet.ts +++ b/src/server/routers/fleet.ts @@ -101,15 +101,13 @@ export const fleetRouter = router({ const since = new Date(now - rangeMs[input.range]); const totalSeconds = rangeMs[input.range] / 1000; - // Get events in range - const events = await prisma.nodeStatusEvent.findMany({ - where: { nodeId: input.nodeId, timestamp: { gte: since } }, - orderBy: { timestamp: "asc" }, - }); - - // Get the last event before the range to know starting status, + // Get events in range, the last event before the range (to know starting status), // and the node's current status as a fallback for nodes with no event history - const [priorEvent, nodeForStatus] = await Promise.all([ + const [events, priorEvent, nodeForStatus] = await Promise.all([ + prisma.nodeStatusEvent.findMany({ + where: { nodeId: input.nodeId, timestamp: { gte: since } }, + orderBy: { timestamp: "asc" }, + }), prisma.nodeStatusEvent.findFirst({ where: { nodeId: input.nodeId, timestamp: { lt: since } }, orderBy: { timestamp: "desc" },