/** * `useHistory` hook — fetches and manages ban history data. */ import { useCallback, useEffect, useRef, useState } from "react"; import { fetchHistory, fetchIpHistory } from "../api/history"; import { handleFetchError } from "../utils/fetchError"; import type { HistoryBanItem, IpDetailResponse } from "../types/history"; import type { BanOriginFilter, TimeRange } from "../types/ban"; // --------------------------------------------------------------------------- // useHistory — paginated list // --------------------------------------------------------------------------- export interface UseHistoryResult { items: HistoryBanItem[]; total: number; page: number; loading: boolean; error: string | null; setPage: (page: number) => void; refresh: () => void; } /** * Fetch and manage paginated ban history with optional filters. * * @param page - Current page number (1-indexed) * @param pageSize - Items per page * @param range - Time range filter (e.g., "7d", "30d") * @param origin - Ban origin filter (e.g., "fail2ban", "blocklist") * @param jail - Jail name filter (optional) * @param ip - IP address filter (optional) * @param source - Data source ("fail2ban" | "archive") * @returns History data with pagination and error state */ export function useHistory( page: number = 1, pageSize: number = 50, range?: TimeRange, origin?: BanOriginFilter, jail?: string, ip?: string, source: "fail2ban" | "archive" = "archive", ): UseHistoryResult { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [currentPage, setCurrentPage] = useState(page); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const abortRef = useRef(null); const load = useCallback((): void => { abortRef.current?.abort(); abortRef.current = new AbortController(); setLoading(true); setError(null); fetchHistory( { page: currentPage, page_size: pageSize, range, origin, jail, ip, source, }, abortRef.current.signal, ) .then((resp) => { setItems(resp.items); setTotal(resp.total); }) .catch((err: unknown) => { handleFetchError(err, setError, "Failed to fetch history"); }) .finally((): void => { setLoading(false); }); }, [currentPage, pageSize, range, origin, jail, ip, source]); useEffect((): (() => void) => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); return { items, total, page: currentPage, loading, error, setPage: setCurrentPage, refresh: load, }; } // --------------------------------------------------------------------------- // useIpHistory — per-IP detail // --------------------------------------------------------------------------- export interface UseIpHistoryResult { detail: IpDetailResponse | null; loading: boolean; error: string | null; refresh: () => void; } export function useIpHistory(ip: string): UseIpHistoryResult { const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const abortRef = useRef(null); const load = useCallback((): void => { abortRef.current?.abort(); abortRef.current = new AbortController(); setLoading(true); setError(null); fetchIpHistory(ip, abortRef.current.signal) .then((resp) => { setDetail(resp); }) .catch((err: unknown) => { handleFetchError(err, setError, "Failed to fetch IP history"); }) .finally((): void => { setLoading(false); }); }, [ip]); useEffect((): (() => void) => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); return { detail, loading, error, refresh: load }; }