"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { authFetch, clearToken, getApiBase, getToken } from "../lib/auth"; type DiagnosticCatalogItem = { key: string; label: string; category: string; description: string; live_safe: boolean; target: string | null; configured: boolean; config_status: string; config_detail: string; }; type DiagnosticResult = { key: string; label: string; category: string; description: string; target: string | null; live_safe: boolean; configured: boolean; status: string; message: string; detail?: unknown; checked_at?: string; duration_ms?: number; }; type DiagnosticsResponse = { checks: DiagnosticCatalogItem[]; categories: string[]; generated_at: string; }; type RunDiagnosticsResponse = { results: DiagnosticResult[]; summary: { total: number; up: number; down: number; degraded: number; not_configured: number; disabled: number; }; checked_at: string; }; type RunMode = "safe" | "all" | "single"; type AdminDiagnosticsPanelProps = { embedded?: boolean; }; type DatabaseDiagnosticDetail = { integrity_check?: string; database_path?: string; database_size_bytes?: number; wal_size_bytes?: number; shm_size_bytes?: number; page_size_bytes?: number; page_count?: number; freelist_pages?: number; allocated_bytes?: number; free_bytes?: number; row_counts?: Record; timings_ms?: Record; }; const REFRESH_INTERVAL_MS = 30000; const STATUS_LABELS: Record = { idle: "Ready", up: "Up", down: "Down", degraded: "Degraded", disabled: "Disabled", not_configured: "Not configured", }; function formatCheckedAt(value?: string) { if (!value) return "Not yet run"; const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) return value; return parsed.toLocaleString(); } function formatDuration(value?: number) { if (typeof value !== "number" || Number.isNaN(value) || value <= 0) { return "Pending"; } return `${value.toFixed(1)} ms`; } function statusLabel(status: string) { return STATUS_LABELS[status] ?? status; } function formatBytes(value?: number) { if (typeof value !== "number" || Number.isNaN(value) || value < 0) { return "0 B"; } if (value >= 1024 * 1024 * 1024) { return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`; } if (value >= 1024 * 1024) { return `${(value / (1024 * 1024)).toFixed(2)} MB`; } if (value >= 1024) { return `${(value / 1024).toFixed(1)} KB`; } return `${value} B`; } function formatDetailLabel(value: string) { return value.replace(/_/g, " ").replace(/\b\w/g, (character) => character.toUpperCase()); } function asDatabaseDiagnosticDetail(detail: unknown): DatabaseDiagnosticDetail | null { if (!detail || typeof detail !== "object" || Array.isArray(detail)) { return null; } return detail as DatabaseDiagnosticDetail; } function renderDatabaseMetricGroup(title: string, values: Array<[string, string]>) { if (values.length === 0) { return null; } return (

{title}

{values.map(([label, value]) => (
{label} {value}
))}
); } export default function AdminDiagnosticsPanel({ embedded = false }: AdminDiagnosticsPanelProps) { const router = useRouter(); const [loading, setLoading] = useState(true); const [authorized, setAuthorized] = useState(false); const [checks, setChecks] = useState([]); const [resultsByKey, setResultsByKey] = useState>({}); const [runningKeys, setRunningKeys] = useState([]); const [autoRefresh, setAutoRefresh] = useState(true); const [pageError, setPageError] = useState(""); const [lastRunAt, setLastRunAt] = useState(null); const [lastRunMode, setLastRunMode] = useState(null); const [emailRecipient, setEmailRecipient] = useState(""); const liveSafeKeys = useMemo(() => checks.filter((check) => check.live_safe).map((check) => check.key), [checks]); const runDiagnostics = useCallback( async (keys?: string[], mode: RunMode = "single") => { const baseUrl = getApiBase(); const effectiveKeys = keys && keys.length > 0 ? keys : checks.map((check) => check.key); if (effectiveKeys.length === 0) { return; } setRunningKeys((current) => Array.from(new Set([...current, ...effectiveKeys]))); setPageError(""); try { const response = await authFetch(`${baseUrl}/admin/diagnostics/run`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ keys: effectiveKeys, ...(emailRecipient.trim() ? { recipient_email: emailRecipient.trim() } : {}), }), }); if (response.status === 401) { clearToken(); router.push("/login"); return; } if (!response.ok) { const text = await response.text(); throw new Error(text || `Diagnostics run failed: ${response.status}`); } const data = (await response.json()) as { status: string } & RunDiagnosticsResponse; const nextResults: Record = {}; for (const result of data.results ?? []) { nextResults[result.key] = result; } setResultsByKey((current) => ({ ...current, ...nextResults })); setLastRunAt(data.checked_at ?? new Date().toISOString()); setLastRunMode(mode); } catch (error) { console.error(error); setPageError(error instanceof Error ? error.message : "Diagnostics run failed."); } finally { setRunningKeys((current) => current.filter((key) => !effectiveKeys.includes(key))); } }, [checks, emailRecipient, router], ); // biome-ignore lint/correctness/useExhaustiveDependencies: Authorization bootstrap runs once for each router instance. useEffect(() => { let active = true; const loadPage = async () => { if (!getToken()) { router.push("/login"); return; } try { const baseUrl = getApiBase(); const authResponse = await authFetch(`${baseUrl}/auth/me`); if (!authResponse.ok) { if (authResponse.status === 401) { clearToken(); router.push("/login"); return; } router.push("/"); return; } const me = await authResponse.json(); if (!active) return; if (me?.role !== "admin") { router.push("/"); return; } const diagnosticsResponse = await authFetch(`${baseUrl}/admin/diagnostics`); if (!diagnosticsResponse.ok) { const text = await diagnosticsResponse.text(); throw new Error(text || `Diagnostics load failed: ${diagnosticsResponse.status}`); } const data = (await diagnosticsResponse.json()) as { status: string } & DiagnosticsResponse; if (!active) return; setChecks(data.checks ?? []); setAuthorized(true); setLoading(false); const safeKeys = (data.checks ?? []).filter((check) => check.live_safe).map((check) => check.key); if (safeKeys.length > 0) { void runDiagnostics(safeKeys, "safe"); } } catch (error) { console.error(error); if (!active) return; setPageError(error instanceof Error ? error.message : "Unable to load diagnostics."); setLoading(false); } }; void loadPage(); return () => { active = false; }; }, [router]); useEffect(() => { if (!authorized || !autoRefresh || liveSafeKeys.length === 0) { return; } const interval = window.setInterval(() => { void runDiagnostics(liveSafeKeys, "safe"); }, REFRESH_INTERVAL_MS); return () => { window.clearInterval(interval); }; }, [authorized, autoRefresh, liveSafeKeys, runDiagnostics]); if (loading) { return
Loading diagnostics...
; } if (!authorized) { return null; } const orderedCategories: string[] = []; for (const check of checks) { if (!orderedCategories.includes(check.category)) { orderedCategories.push(check.category); } } const mergedResults = checks.map((check) => { const result = resultsByKey[check.key]; if (result) { return result; } return { key: check.key, label: check.label, category: check.category, description: check.description, target: check.target, live_safe: check.live_safe, configured: check.configured, status: check.configured ? "idle" : check.config_status, message: check.configured ? "Ready to test." : check.config_detail, checked_at: undefined, duration_ms: undefined, } satisfies DiagnosticResult; }); const summary = { total: mergedResults.length, up: 0, down: 0, degraded: 0, disabled: 0, not_configured: 0, idle: 0, }; for (const result of mergedResults) { const key = result.status as keyof typeof summary; if (key in summary) { summary[key] += 1; } } return (

{embedded ? "Connectivity diagnostics" : "Control center"}

Check Magent and your connected services. Automatic refresh runs health checks only. Test messages are managed in Notifications below.

{autoRefresh ? "Auto refresh on" : "Auto refresh off"} {lastRunMode ? `Last run: ${lastRunMode}` : "No run yet"}
Total {summary.total}
Up {summary.up}
Degraded {summary.degraded}
Down {summary.down}
Disabled {summary.disabled}
Not configured {summary.not_configured}
Last completed run: {formatCheckedAt(lastRunAt ?? undefined)}
{pageError ?
{pageError}
: null} {orderedCategories.map((category) => { const categoryChecks = mergedResults.filter((check) => check.category === category); return (

{category}

{category === "Notifications" ? "These tests can emit real messages." : "Safe live health checks."}

{categoryChecks.length} checks
{category === "Notifications" && (

Choose where the test email goes. Other channels use their configured destinations.

)}
{categoryChecks.map((check) => { const isRunning = runningKeys.includes(check.key); return (

{check.label}

{statusLabel(check.status)}

{check.description}

Target {check.target || "Not set"}
Latency {formatDuration(check.duration_ms)}
Mode {check.live_safe ? "Live safe" : "Manual only"}
Last checked {formatCheckedAt(check.checked_at)}
{isRunning ? "Running diagnostic..." : check.message}
{check.key === "database" ? (() => { const detail = asDatabaseDiagnosticDetail(check.detail); if (!detail) { return null; } return (
Database storage, tables and timings {renderDatabaseMetricGroup("Storage", [ ["Database file", formatBytes(detail.database_size_bytes)], ["WAL file", formatBytes(detail.wal_size_bytes)], ["Shared memory", formatBytes(detail.shm_size_bytes)], ["Allocated bytes", formatBytes(detail.allocated_bytes)], ["Free bytes", formatBytes(detail.free_bytes)], ["Page size", formatBytes(detail.page_size_bytes)], ["Page count", `${detail.page_count?.toLocaleString() ?? 0}`], ["Freelist pages", `${detail.freelist_pages?.toLocaleString() ?? 0}`], ])} {renderDatabaseMetricGroup( "Tables", Object.entries(detail.row_counts ?? {}).map(([key, value]) => [ formatDetailLabel(key), value.toLocaleString(), ]), )} {renderDatabaseMetricGroup( "Timings", Object.entries(detail.timings_ms ?? {}).map(([key, value]) => [ formatDetailLabel(key), `${value.toFixed(1)} ms`, ]), )}
); })() : null}
); })}
); })}
); }