import { useEffect, useState } from 'react'; import { Check, CircleHelp, Database, X, type LucideIcon } from 'lucide-react'; import { apiFetch } from '@/lib/api'; import { cn } from '@/lib/utils'; import { useNodes } from '@/context/NodeContext'; // Mirrors the backend payload shape (the frontend never imports backend). type RollbackItemState = 'ready' | 'missing' | 'unknown' | 'not_covered'; type RollbackOverall = 'ready' | 'partial' | 'not_ready'; interface RollbackReadinessItem { id: string; state: RollbackItemState; label: string; detail: string; } interface RollbackReadinessReport { stack: string; computedAt: number; overall: RollbackOverall; items: RollbackReadinessItem[]; } const LABEL_CLASS = 'font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle'; const OVERALL_META: Record = { ready: { label: 'ready', tone: 'border-success/40 bg-success/[0.06] text-success' }, partial: { label: 'partial', tone: 'border-warning/40 bg-warning/[0.06] text-warning' }, not_ready: { label: 'not ready', tone: 'border-destructive/40 bg-destructive/[0.06] text-destructive' }, }; const STATE_META: Record = { ready: { icon: Check, tone: 'text-success' }, missing: { icon: X, tone: 'text-destructive' }, unknown: { icon: CircleHelp, tone: 'text-stat-subtitle' }, not_covered: { icon: Database, tone: 'text-warning' }, }; /** * "Would the existing rollback actually save me?" disclosure for the Stack * Dossier. Read-only; renders nothing while loading, on error, or when the * active node does not advertise the update-guard capability. */ export function RollbackReadinessSection({ stackName }: { stackName: string }) { const { activeNode, hasCapability } = useNodes(); const nodeId = activeNode?.id; const enabled = hasCapability('update-guard'); const [report, setReport] = useState(null); useEffect(() => { setReport(null); if (!enabled) return; let cancelled = false; const load = async () => { try { const res = await apiFetch(`/stacks/${stackName}/rollback-readiness`); if (cancelled) return; if (!res.ok) { console.warn('[RollbackReadiness] unavailable for %s:', stackName, res.status); return; } setReport(await res.json() as RollbackReadinessReport); } catch (e) { // Render-nothing on failure: the dossier remains fully usable without // this section. The warn keeps the cause findable in the console. if (!cancelled) console.warn('[RollbackReadiness] unavailable for %s:', stackName, e); } }; void load(); return () => { cancelled = true; }; }, [stackName, nodeId, enabled]); if (!enabled || !report) return null; const overall = OVERALL_META[report.overall] ?? OVERALL_META.partial; return (
rollback readiness {overall.label}
{report.items.map(item => { const meta = STATE_META[item.state] ?? STATE_META.unknown; const StateIcon = meta.icon; return (
{item.label}
{item.detail}
); })}
); }