import React, { useEffect, useMemo, useState } from "react"; import Modal from "./Modal"; import { Badge, ButtonSoft, Card } from "./ui"; import { apiGetJson } from "../api/client"; import type { ApiHealthResponse, ApiObservabilityMetricsResponse } from "../api/contracts"; export default function HealthStatusModal({ open, onClose, }: { open: boolean; onClose: () => void; }) { const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [metrics, setMetrics] = useState(null); const [error, setError] = useState(""); const envSorted = useMemo(() => { if (!data?.env) return [] as Array<[string, boolean]>; return Object.entries(data.env).sort((a, b) => a[0].localeCompare(b[0])); }, [data]); const authAllowedDomains = useMemo(() => { const domains = Array.isArray(data?.auth?.allowed_domains) ? data?.auth?.allowed_domains : []; return [...domains].sort((a, b) => String(a).localeCompare(String(b))); }, [data]); const fetchHealth = async () => { setLoading(true); setError(""); try { const [healthJson, metricsJson] = await Promise.all([ apiGetJson("/api/health", { cache: "no-store" }), apiGetJson("/api/observability/metrics?limit=25", { cache: "no-store" }).catch( () => null ), ]); setData(healthJson); setMetrics(metricsJson); } catch (e: any) { setError(e?.message || String(e)); setData(null); setMetrics(null); } finally { setLoading(false); } }; useEffect(() => { if (!open) return; // Fetch on open. fetchHealth(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); const overallBadge = data?.ok ? ( Healthy ) : ( Issues found ); return (
{data ? overallBadge : Unknown} {data?.summary ? ( Required checks: {Number(data.summary.required_ok || 0)}/{Number(data.summary.required_total || 0)} ) : null}
{loading ? "Refreshing…" : "Refresh"} Close
{error ? (
Failed to load status: {error}
) : null}
Checks
{!data ? (
No data loaded yet.
) : (
{data.checks.map((c, idx) => (
{c.name}
{c.ok ? OK : Fail}
{[c.kind, c.path].filter(Boolean).join(" • ")}
{c.error ?
{c.error}
: null}
))}
)}
Environment (presence only)
{!data ? (
No data loaded yet.
) : (
{envSorted.map(([k, present]) => (
{k}
{present ? Present : Missing}
))}
)}
Authentication
{!data?.auth ? (
Auth diagnostics unavailable in this response.
) : (
{data.auth.required ? Auth required : Auth optional} {data.auth.enabled ? Auth enabled : Auth not enabled}
Issuer: {data.auth.issuer || "Not configured"}
Audience: {data.auth.audience || "Not configured"}
Email claim: {data.auth.email_claim || "Not configured"}
Allowed domains:{" "} {authAllowedDomains.length ? authAllowedDomains.join(", ") : "Not configured"}
{data.auth.config_error ? (
Config error
{data.auth.config_error}
) : null} {Array.isArray(data.auth.config_details) && data.auth.config_details.length ? (
Config details
{data.auth.config_details.map((line, idx) => (
• {line}
))}
) : null} {Array.isArray(data.auth.config_warnings) && data.auth.config_warnings.length ? (
Config warnings
{data.auth.config_warnings.map((line, idx) => (
• {line}
))}
) : null}
)}
{metrics ? (
API observability
Requests: {metrics.totals.requests} • Errors: {metrics.totals.errors} • Error rate:{" "} {(metrics.totals.error_rate * 100).toFixed(2)}%
{metrics.endpoints.slice(0, 8).map((m) => (
{m.endpoint}
{m.error_requests > 0 ? ( {m.error_requests} errors ) : ( No errors )}
req={m.total_requests} • avg={m.avg_latency_ms}ms • p95={m.p95_latency_ms}ms • max={m.max_latency_ms}ms
))}
) : null}
); }