From f023f7782bb889b1c3dc8910f2ce6c5ac4f1704b Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Sun, 15 Mar 2026 12:33:52 +0000 Subject: [PATCH 1/2] fix: use node current status as fallback instead of hardcoded UNKNOWN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getStatusTimeline returns { events, nodeStatus } — fetches node's current DB status in parallel so the timeline shows actual status when no events exist - getUptime falls back to nodeForStatus.status before "UNKNOWN" for nodes with no prior event history, preventing 0.00% uptime for healthy nodes - StatusTimeline uses data.nodeStatus for the empty-range segment instead of hardcoded "UNKNOWN", fixing the grey/unknown display for healthy nodes Fixes VF-5: uptime shows 0.00 (red) and status timeline shows 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 e5e6d4a8fdf7fd7edcb787fe800a15864c144c77 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Sun, 15 Mar 2026 15:51:05 +0000 Subject: [PATCH 2/2] 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 (