/** * React hook for fetching a single jail's detailed metadata. */ import { useCallback, useEffect, useRef, useState } from "react"; import { addIgnoreIp, delIgnoreIp, fetchJail, reloadJail, setJailIdle, startJail, stopJail, toggleIgnoreSelf as toggleIgnoreSelfApi } from "../api/jails"; import { handleFetchError } from "../utils/fetchError"; import type { Jail } from "../types/jail"; export interface UseJailDetailResult { jail: Jail | null; ignoreList: string[]; ignoreSelf: boolean; loading: boolean; error: string | null; refresh: () => void; addIp: (ip: string) => Promise; removeIp: (ip: string) => Promise; toggleIgnoreSelf: (on: boolean) => Promise; start: () => Promise; stop: () => Promise; reload: () => Promise; setIdle: (on: boolean) => Promise; } /** * Fetch and manage the detail view for a single jail. */ export function useJailDetail(name: string): UseJailDetailResult { 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 load = 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, setError, "Failed to fetch jail detail"); } }) .finally(() => { if (!ctrl.signal.aborted) { setLoading(false); } }); }, [name]); useEffect(() => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); const addIp = useCallback(async (ip: string): Promise => { await addIgnoreIp(name, ip); load(); }, [name, load]); const removeIp = useCallback(async (ip: string): Promise => { await delIgnoreIp(name, ip); load(); }, [name, load]); const toggleIgnoreSelf = useCallback(async (on: boolean): Promise => { await toggleIgnoreSelfApi(name, on); load(); }, [name, load]); const start = useCallback(async (): Promise => { await startJail(name); load(); }, [name, load]); const stop = useCallback(async (): Promise => { await stopJail(name); load(); }, [name, load]); const reload = useCallback(async (): Promise => { await reloadJail(name); load(); }, [name, load]); const setIdle = useCallback(async (on: boolean): Promise => { await setJailIdle(name, on); load(); }, [name, load]); return { jail, ignoreList, ignoreSelf, loading, error, refresh: load, addIp, removeIp, toggleIgnoreSelf, start, stop, reload, setIdle, }; }