/** * React hook for loading the jail config inventory. */ import { useCallback, useEffect, useRef, useState } from "react"; import { fetchJailConfigs, reloadConfig, updateJailConfig } from "../api/config"; import { handleFetchError } from "../utils/fetchError"; import type { JailConfig, JailConfigUpdate } from "../types/config"; export interface UseJailConfigsResult { jails: JailConfig[]; total: number; loading: boolean; error: string | null; refresh: () => void; updateJail: (name: string, update: JailConfigUpdate) => Promise; reloadAll: () => Promise; } /** * Load all jail configs and expose update controls. */ export function useJailConfigs(): UseJailConfigsResult { const [jails, setJails] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const abortRef = useRef(null); const load = useCallback((): void => { abortRef.current?.abort(); const ctrl = new AbortController(); abortRef.current = ctrl; setLoading(true); setError(null); fetchJailConfigs() .then((resp) => { if (!ctrl.signal.aborted) { setJails(resp.jails); setTotal(resp.total); } }) .catch((err: unknown) => { if (!ctrl.signal.aborted) { handleFetchError(err, setError, "Failed to fetch jail configs"); } }) .finally(() => { if (!abortRef.current?.signal.aborted) { setLoading(false); } }); }, []); useEffect(() => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); const updateJail = useCallback( async (name: string, update: JailConfigUpdate): Promise => { await updateJailConfig(name, update); load(); }, [load], ); const reloadAll = useCallback(async (): Promise => { await reloadConfig(); load(); }, [load]); return { jails, total, loading, error, refresh: load, updateJail, reloadAll }; }