Refactor frontend API calls into hooks and complete task states

This commit is contained in:
2026-03-20 15:18:04 +01:00
parent d30d138146
commit 28a7610276
16 changed files with 483 additions and 409 deletions

View File

@@ -13,6 +13,7 @@ import {
delIgnoreIp,
fetchActiveBans,
fetchJail,
fetchJailBannedIps,
fetchJails,
lookupIp,
reloadAllJails,
@@ -261,6 +262,107 @@ export function useJailDetail(name: string): UseJailDetailResult {
};
}
// ---------------------------------------------------------------------------
// useJailBannedIps
export interface UseJailBannedIpsResult {
items: ActiveBan[];
total: number;
page: number;
pageSize: number;
search: string;
loading: boolean;
error: string | null;
opError: string | null;
refresh: () => Promise<void>;
setPage: (page: number) => void;
setPageSize: (size: number) => void;
setSearch: (term: string) => void;
unban: (ip: string) => Promise<void>;
}
export function useJailBannedIps(jailName: string): UseJailBannedIpsResult {
const [items, setItems] = useState<ActiveBan[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(25);
const [search, setSearch] = useState("");
const [debouncedSearch, setDebouncedSearch] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [opError, setOpError] = useState<string | null>(null);
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const load = useCallback(async (): Promise<void> => {
if (!jailName) {
setItems([]);
setTotal(0);
setLoading(false);
return;
}
setLoading(true);
setError(null);
try {
const resp = await fetchJailBannedIps(jailName, page, pageSize, debouncedSearch || undefined);
setItems(resp.items);
setTotal(resp.total);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [jailName, page, pageSize, debouncedSearch]);
useEffect(() => {
if (debounceRef.current !== null) {
clearTimeout(debounceRef.current);
}
debounceRef.current = setTimeout(() => {
setDebouncedSearch(search);
setPage(1);
}, 300);
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
return () => {
if (debounceRef.current !== null) {
clearTimeout(debounceRef.current);
}
};
}, [search]);
useEffect(() => {
void load();
}, [load]);
const unban = useCallback(async (ip: string): Promise<void> => {
setOpError(null);
try {
await unbanIp(ip, jailName);
await load();
} catch (err: unknown) {
setOpError(err instanceof Error ? err.message : String(err));
}
}, [jailName, load]);
return {
items,
total,
page,
pageSize,
search,
loading,
error,
opError,
refresh: load,
setPage,
setPageSize,
setSearch,
unban,
};
}
// ---------------------------------------------------------------------------
// useActiveBans — live ban list
// ---------------------------------------------------------------------------