import { useState, useEffect } from 'react'; import { Skeleton } from '@/components/ui/skeleton'; import { RefreshCw } from 'lucide-react'; import { apiFetch } from '@/lib/api'; import { toast } from '@/components/ui/toast-store'; import { useNodes } from '@/context/NodeContext'; import { useAuth } from '@/context/AuthContext'; import { useExperimental } from '@/hooks/useExperimental'; import { DEFAULT_SETTINGS } from './types'; import type { PatchableSettings } from './types'; import { SettingsSection } from './SettingsSection'; import { SettingsField } from './SettingsField'; import { SettingsActions, SettingsPrimaryButton } from './SettingsActions'; import { useMastheadStats } from './MastheadStatsContext'; import { useSettingsDirty } from './useSettingsDirty'; import { useNodeSettingsLoad } from './useNodeSettingsLoad'; import { SettingsLoadGate } from './SettingsLoadError'; import { TogglePill } from '@/components/ui/toggle-pill'; interface FleetMeshSectionProps { onDirtyChange?: (dirty: boolean) => void; } function SectionSkeleton() { return (
); } type FleetMeshFields = Pick; type SnapshotOnlyFields = Pick; const DEFAULT_FLEET_MESH: FleetMeshFields = { mesh_auto_recreate: DEFAULT_SETTINGS.mesh_auto_recreate, snapshot_documentation: DEFAULT_SETTINGS.snapshot_documentation, }; export function FleetMeshSection({ onDirtyChange }: FleetMeshSectionProps) { const { activeNode } = useNodes(); const { isAdmin } = useAuth(); const { experimental, experimentalReady } = useExperimental(); const showMesh = experimentalReady && experimental; const readOnly = !isAdmin; const { settings, setSettings, dirtyCount, hasChanges, reset, markSaved } = useSettingsDirty({ ...DEFAULT_FLEET_MESH }); const { phase, isCurrentNodeLoaded, load, isSaveOwner, captureSaveGuard } = useNodeSettingsLoad(activeNode?.id); const [isSaving, setIsSaving] = useState(false); const reportDirty = isCurrentNodeLoaded && hasChanges; useEffect(() => { onDirtyChange?.(reportDirty); }, [reportDirty, onDirtyChange]); useMastheadStats( !isCurrentNodeLoaded ? null : [ { label: 'EDITED', value: hasChanges ? `${dirtyCount} pending` : 'saved', tone: hasChanges ? 'warn' : 'value', }, ], ); useEffect(() => { let cancelled = false; setIsSaving(false); void (async () => { const nodeData = await load(); if (cancelled || !nodeData) return; const safe: FleetMeshFields = { mesh_auto_recreate: (nodeData.mesh_auto_recreate as '0' | '1') ?? DEFAULT_SETTINGS.mesh_auto_recreate, snapshot_documentation: (nodeData.snapshot_documentation as '0' | '1') ?? DEFAULT_SETTINGS.snapshot_documentation, }; reset(safe); })(); return () => { cancelled = true; }; }, [activeNode?.id, load, reset]); const onSettingChange = (key: K, value: FleetMeshFields[K]) => { setSettings(prev => ({ ...prev, [key]: value })); }; const saveSettings = async () => { // When Mesh discovery is off, never write mesh_auto_recreate: a failed // settings read would otherwise push the default and overwrite a real // Mesh config the operator cannot see. const saveGuard = captureSaveGuard(); const submitted: FleetMeshFields | SnapshotOnlyFields = showMesh ? { ...settings } : { snapshot_documentation: settings.snapshot_documentation }; setIsSaving(true); try { const res = await apiFetch('/settings', { method: 'PATCH', nodeId: saveGuard.nodeId, body: JSON.stringify(submitted), }); if (!isSaveOwner(saveGuard)) return; if (!res.ok) { const err = await res.json().catch(() => ({})); toast.error(err?.error || err?.message || 'Failed to save settings.'); return; } if (showMesh) { markSaved(submitted as FleetMeshFields); } else { markSaved({ ...settings, snapshot_documentation: (submitted as SnapshotOnlyFields).snapshot_documentation, }); } toast.success('Fleet settings saved.'); } catch (e: unknown) { if (!isSaveOwner(saveGuard)) return; toast.error((e as Error)?.message || 'Something went wrong.'); } finally { if (isSaveOwner(saveGuard)) setIsSaving(false); } }; return ( }>
{showMesh && ( onSettingChange('mesh_auto_recreate', next ? '1' : '0')} /> )} onSettingChange('snapshot_documentation', next ? '1' : '0')} /> {!readOnly && ( {isSaving ? ( <> Saving ) : ( 'Save settings' )} )}
); }