/** * useConfigActiveStatus — compute active status for jails, filters, and * actions by correlating runtime jail data with their config files. * * Active determination rules: * - Jail: `enabled === true` in the live JailSummary list. * - Filter: referenced by at least one enabled jail (jail name = filter name * by fail2ban convention, unless a filter field is provided). * - Action: referenced in the `actions` array of at least one enabled jail's * JailConfig. */ import { useCallback, useEffect, useRef, useState } from "react"; import { fetchJails } from "../api/jails"; import { fetchJailConfigs } from "../api/config"; import { handleFetchError } from "../utils/fetchError"; import type { JailConfig } from "../types/config"; import type { JailSummary } from "../types/jail"; // --------------------------------------------------------------------------- // Public interface // --------------------------------------------------------------------------- export interface UseConfigActiveStatusResult { /** Names of jails that are currently enabled. */ activeJails: Set; /** Names of filter files referenced by at least one enabled jail. */ activeFilters: Set; /** Names of action files referenced by at least one enabled jail. */ activeActions: Set; /** True while any parallel fetch is in progress. */ loading: boolean; /** Error message from any failed fetch, or null. */ error: string | null; /** Re-fetch all data sources. */ refresh: () => void; } // --------------------------------------------------------------------------- // Hook // --------------------------------------------------------------------------- /** * Fetch jails and jail configs, then derive active-status sets for each * config type. Active status is computed from live jail data; filter and * action files are not fetched directly because their active state is already * available via {@link fetchFilters} / {@link fetchActions}. * * @returns Active-status sets, loading flag, error, and refresh function. */ export function useConfigActiveStatus(): UseConfigActiveStatusResult { const [activeJails, setActiveJails] = useState>(new Set()); const [activeFilters, setActiveFilters] = useState>(new Set()); const [activeActions, setActiveActions] = useState>(new Set()); 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); Promise.all([ fetchJails(), fetchJailConfigs(), ]) .then(([jailsResp, configsResp]) => { if (ctrl.signal.aborted) return; const summaries: JailSummary[] = jailsResp.jails; const configs: JailConfig[] = configsResp.jails; // Active jails: enabled in the runtime summary list. const jailSet = new Set( summaries.filter((j) => j.enabled).map((j) => j.name), ); // Active filters: referenced by any enabled jail. // By fail2ban convention the filter name equals the jail name unless // the config explicitly declares a [filter] section (not surfaced in // JailConfig), so we use the jail name as the filter name. const filterSet = new Set(); for (const cfg of configs) { if (jailSet.has(cfg.name)) { filterSet.add(cfg.name); } } // Active actions: referenced in the actions array of any enabled jail. const actionSet = new Set(); for (const cfg of configs) { if (jailSet.has(cfg.name)) { for (const action of cfg.actions) { // Actions can be stored as "name[param=val]" — extract base name. const bracketIdx = action.indexOf("["); const baseName = (bracketIdx >= 0 ? action.slice(0, bracketIdx) : action).trim(); actionSet.add(baseName); } } } setActiveJails(jailSet); setActiveFilters(filterSet); setActiveActions(actionSet); setLoading(false); }) .catch((err: unknown) => { if (ctrl.signal.aborted) return; handleFetchError(err, setError, "Failed to load active status."); setLoading(false); }); }, []); useEffect(() => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); return { activeJails, activeFilters, activeActions, loading, error, refresh: load, }; }