import { useQuery } from "@tanstack/react-query"; import { Clock3, FileDiff, GitCommit, type LucideIcon } from "lucide-react"; import { healthApi, type HealthStatus } from "@/api/health"; import { instanceSettingsApi } from "@/api/instanceSettings"; import { queryKeys } from "@/lib/queryKeys"; function formatTimestamp(value: string | null | undefined): string { if (!value) return "Unavailable"; const date = new Date(value); if (Number.isNaN(date.getTime())) return "Unavailable"; return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short", }).format(date); } function isValidTimestamp(value: string | null | undefined): value is string { return !!value && !Number.isNaN(new Date(value).getTime()); } function restartTimestamp(health: HealthStatus | undefined): string | null { return health?.devServer?.lastRestartAt ?? health?.serverInfo?.processStartedAt ?? null; } function commitLabel(health: HealthStatus | undefined): string { const git = health?.serverInfo?.git; if (!git?.available) return "Commit unavailable"; return `${git.shortSha} ยท ${git.subject}`; } function localChangesLabel(health: HealthStatus | undefined): string { const git = health?.serverInfo?.git; if (!git?.available) return "Unavailable"; const localChanges = git.localChanges; if (!localChanges) return "Change status unavailable"; if (!localChanges.available) return "Change status unavailable"; if (!localChanges.hasLocalChanges) return "Clean checkout"; const parts = [ [localChanges.stagedFileCount, "staged"], [localChanges.unstagedFileCount, "unstaged"], [localChanges.untrackedFileCount, "untracked"], ] .filter(([count]) => Number(count) > 0) .map(([count, label]) => `${count} ${label}`); return parts.length > 0 ? `Local changes present (${parts.join(", ")})` : "Local changes present"; } function ServerInfoRow({ icon: Icon, label, value, dateTime, }: { icon: LucideIcon; label: string; value: string; dateTime?: string | null; }) { return (
{label} {dateTime ? ( ) : ( {value} )}
); } export function SidebarServerInfo() { const experimentalQuery = useQuery({ queryKey: queryKeys.instance.experimentalSettings, queryFn: () => instanceSettingsApi.getExperimental(), }); const enabled = experimentalQuery.data?.enableServerInfoDebugView === true; const healthQuery = useQuery({ queryKey: queryKeys.health, queryFn: () => healthApi.get(), enabled, // The drawer only mounts while the account popover is open, so it cannot // rely on Layout's background health poll (which is itself gated on // devServer.enabled). Always refetch on open and poll while open so a server // restart is reflected without leaving stale boot-time serverInfo on screen. refetchOnMount: "always", refetchInterval: (query) => { const data = query.state.data as HealthStatus | undefined; return data?.devServer?.enabled ? 2000 : false; }, }); if (!enabled) return null; const health = healthQuery.data; const isWaitingForHealth = healthQuery.isLoading && !health; const healthUnavailable = healthQuery.isError; const restartedAt = restartTimestamp(health); const restartedAtIsValid = isValidTimestamp(restartedAt); const lastRestartedLabel = healthUnavailable ? "Health unavailable" : isWaitingForHealth ? "Loading..." : formatTimestamp(restartedAt); const commit = healthUnavailable ? "Health unavailable" : isWaitingForHealth ? "Loading..." : commitLabel(health); const localChanges = healthUnavailable ? "Health unavailable" : isWaitingForHealth ? "Loading..." : localChangesLabel(health); return (

Server

); }