import { useState, useEffect, useCallback } from 'react'; import type { ReactNode } from 'react'; import { RefreshCw, Check, AlertTriangle, X } from 'lucide-react'; import { apiFetch } from '@/lib/api'; import { toast } from '@/components/ui/toast-store'; import { cn } from '@/lib/utils'; import { Skeleton } from '@/components/ui/skeleton'; import { SettingsActions, SettingsSecondaryButton } from './SettingsActions'; // Shape mirrors the backend EnvironmentReport (services/EnvironmentCheckService.ts); // kept local because the frontend cannot import backend types. The panel only // reads checks, so `remediation` stays optional here even though the backend // models it as required on every warn / fail row. type CheckStatus = 'pass' | 'warn' | 'fail'; type CheckId = 'docker_socket' | 'docker_compose' | 'compose_dir' | 'path_mapping' | 'tls' | 'disk_space'; interface EnvironmentCheck { id: CheckId; label: string; status: CheckStatus; detail: string; remediation?: string; } interface EnvironmentReport { checks: EnvironmentCheck[]; generatedAt: number; } const STATUS_WORD: Record = { pass: 'OK', warn: 'Warning', fail: 'Action needed' }; function StatusBadge({ status, children }: { status: CheckStatus; children: ReactNode }) { const Icon = status === 'pass' ? Check : status === 'warn' ? AlertTriangle : X; return ( {children} ); } function CheckRow({ check }: { check: EnvironmentCheck }) { return (
{check.label} {STATUS_WORD[check.status]}

{check.detail}

{check.remediation ? (

{check.remediation}

) : null}
); } function ChecksSkeleton() { return (
{[0, 1, 2, 3, 4, 5].map(i => )}
); } /** * Preflight environment checks (Docker engine + Compose, the compose directory * and its host path mapping, TLS, disk headroom) with inline remediation. * Layout-neutral so it renders both inside the Recovery settings tab and as the * final step of the setup wizard. Self-contained: fetches on mount and exposes * a Re-run control. It never blocks; the caller decides what continue action, * if any, sits alongside it. */ export function EnvironmentChecks({ className }: { className?: string }) { const [report, setReport] = useState(null); const [isLoading, setIsLoading] = useState(true); const load = useCallback(async () => { setIsLoading(true); try { const res = await apiFetch('/diagnostics/environment', { localOnly: true }); if (!res.ok) { const err = await res.json().catch(() => ({})); toast.error(err?.error || 'Failed to run environment checks.'); setReport(null); return; } setReport(await res.json() as EnvironmentReport); } catch (e: unknown) { toast.error((e as Error)?.message || 'Failed to run environment checks.'); setReport(null); } finally { setIsLoading(false); } }, []); useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect void load(); }, [load]); return (
{isLoading ? ( ) : report ? (
{report.checks.map(check => )}
) : (

Checks could not be run. Try again.

)} void load()} disabled={isLoading}> Re-run
); }