135 lines
4.7 KiB
TypeScript
135 lines
4.7 KiB
TypeScript
/**
|
|
* 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<string>;
|
|
/** Names of filter files referenced by at least one enabled jail. */
|
|
activeFilters: Set<string>;
|
|
/** Names of action files referenced by at least one enabled jail. */
|
|
activeActions: Set<string>;
|
|
/** 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<Set<string>>(new Set());
|
|
const [activeFilters, setActiveFilters] = useState<Set<string>>(new Set());
|
|
const [activeActions, setActiveActions] = useState<Set<string>>(new Set());
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const abortRef = useRef<AbortController | null>(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<string>(
|
|
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<string>();
|
|
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<string>();
|
|
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,
|
|
};
|
|
}
|