/** * React hook for fetching a single jail's detailed metadata. * * Reads jail data: configuration, ignore list, ignore self flag, and related state. * Does not handle mutations — use `useJailCommands` for write operations. */ import { useCallback, useEffect, useRef, useState } from "react"; import { fetchJail } from "../api/jails"; import { handleFetchError, createStringErrorAdapter } from "../utils/fetchError"; import type { Jail } from "../types/jail"; export interface UseJailDataResult { jail: Jail | null; ignoreList: string[]; ignoreSelf: boolean; loading: boolean; error: string | null; refresh: () => void; } /** * Fetch and manage the detail view for a single jail. * * @param name - The name of the jail to fetch. * @returns Jail data and refresh function. */ export function useJailData(name: string): UseJailDataResult { const [jail, setJail] = useState(null); const [ignoreList, setIgnoreList] = useState([]); const [ignoreSelf, setIgnoreSelf] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const abortRef = useRef(null); const refresh = useCallback(() => { abortRef.current?.abort(); const ctrl = new AbortController(); abortRef.current = ctrl; setLoading(true); setError(null); fetchJail(name) .then((res) => { if (!ctrl.signal.aborted) { setJail(res.jail); setIgnoreList(res.ignore_list); setIgnoreSelf(res.ignore_self); } }) .catch((err: unknown) => { if (!ctrl.signal.aborted) { handleFetchError(err, createStringErrorAdapter(setError), "Failed to fetch jail detail"); } }) .finally(() => { if (!ctrl.signal.aborted) { setLoading(false); } }); }, [name]); useEffect(() => { refresh(); return (): void => { abortRef.current?.abort(); }; }, [refresh]); return { jail, ignoreList, ignoreSelf, loading, error, refresh, }; }