"use client"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useMemo, useState } from "react"; import { AgentLink } from "@/components/AgentLink"; import { CheckStatusSummary } from "@/components/CheckStatusSummary"; import { ClientPager } from "@/components/ClientPager"; import { EmptyPanel, SummaryCell, Td, Th } from "@/components/DataPanel"; import { DetailTabs } from "@/components/DetailTabs"; import { PageSizeSelect } from "@/components/PageSizeSelect"; import { Pager } from "@/components/Pager"; import { DateTime } from "@/components/TimeZoneControls"; import type { components } from "@/lib/api-types"; import { groupChecks, statusSeverity, type Agent, type CheckGroup } from "@/lib/check-groups"; import { statusBadgeClass } from "@/lib/format"; import { DEFAULT_PAGE_SIZE, clampPage, pageCountOf, paginate, type PageSize, } from "@/lib/pagination"; import { usePolling } from "@/lib/usePolling"; import type { InventoryData } from "@/lib/view-types"; type StoredEvent = components["schemas"]["StoredCheckResultEvent"]; type EventsPageData = { items: StoredEvent[]; next_cursor?: string | null; prev_cursor?: string | null }; type TabKey = "agents" | "events"; export function CheckDetailBrowser({ checkId, initialData, initialEvents, tab, eventsLimit, eventsCursor, eventsBack, eventsPage, }: { checkId: string; initialData: InventoryData; initialEvents: EventsPageData; tab: TabKey; eventsLimit: PageSize; eventsCursor?: string; eventsBack: boolean; eventsPage: number; }) { const { data } = usePolling(initialData, "/api/poll/inventory"); const agentsById = useMemo( () => new Map(data.agents.map((agent) => [agent.agent_id, agent])), [data.agents], ); const group = useMemo( () => groupChecks(data.checks, agentsById).find((item) => item.checkId === checkId), [agentsById, checkId, data.checks], ); if (!group) { return (
← checks

{checkId}

Check not found.
); } return (
← checks

{checkId}

status
agents
{group.checks.length}
last_observed_at
{tab === "agents" ? ( ) : ( )}
); } function AgentsForCheck({ group }: { group: CheckGroup }) { const rows = useMemo( () => [...group.checks].sort( (a, b) => statusSeverity[b.check.status] - statusSeverity[a.check.status] || hostName(a).localeCompare(hostName(b)), ), [group.checks], ); const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); const [page, setPage] = useState(1); const onPageSizeChange = (next: PageSize) => { setPageSize(next); setPage(1); }; const pageCount = pageCountOf(rows.length, pageSize); const currentPage = clampPage(page, pageCount); const visible = paginate(rows, currentPage, pageSize); return (
{visible.map(({ check, agent }) => ( ))}
HOST STATUS LAST_OBSERVED_AT SUMMARY
{check.status}
); } function EventsForCheck({ checkId, initialData, agentsById, eventsLimit, eventsCursor, eventsBack, eventsPage, }: { checkId: string; initialData: EventsPageData; agentsById: Map; eventsLimit: PageSize; eventsCursor?: string; eventsBack: boolean; eventsPage: number; }) { const router = useRouter(); const pollUrl = useMemo(() => { const qs = new URLSearchParams({ check_id: checkId, limit: String(eventsLimit), }); if (eventsCursor) qs.set("cursor", eventsCursor); if (eventsBack) qs.set("back", "1"); return `/api/poll/events?${qs.toString()}`; }, [checkId, eventsCursor, eventsBack, eventsLimit]); const { data } = usePolling(initialData, pollUrl); const onLimitChange = (next: PageSize) => { router.push( `/checks/${encodeURIComponent(checkId)}?tab=events&events_limit=${next}`, ); }; return (
{data.items.length === 0 ? ( ) : ( {data.items.map((event) => { const agent = agentsById.get(event.agent_id); return ( ); })}
OBSERVED_AT RECEIVED_AT HOST STATUS DURATION_MS SUMMARY
{event.status} {event.duration_ms}
)}
); } function hostName(row: { agent?: Agent; check: { agent_id: string } }): string { return row.agent?.hostname || row.check.agent_id; }