109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
/**
|
|
* `useBans` hook.
|
|
*
|
|
* Fetches and manages paginated ban-list data from the dashboard endpoint.
|
|
* Re-fetches automatically when `timeRange` or `page` changes.
|
|
*/
|
|
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { fetchBans } from "../api/dashboard";
|
|
import { handleFetchError } from "../utils/fetchError";
|
|
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
|
|
|
/** Items per page for the ban table. */
|
|
const PAGE_SIZE = 100;
|
|
|
|
/** Return value shape for {@link useBans}. */
|
|
export interface UseBansResult {
|
|
/** Ban items for the current page. */
|
|
banItems: DashboardBanItem[];
|
|
/** Total records in the selected time window (for pagination). */
|
|
total: number;
|
|
/** Current 1-based page number. */
|
|
page: number;
|
|
/** Navigate to a specific page. */
|
|
setPage: (p: number) => void;
|
|
/** Whether a fetch is currently in flight. */
|
|
loading: boolean;
|
|
/** Error message if the last fetch failed, otherwise `null`. */
|
|
error: string | null;
|
|
/** Imperatively re-fetch the current page. */
|
|
refresh: () => void;
|
|
}
|
|
|
|
/**
|
|
* Fetch and manage dashboard ban-list data.
|
|
*
|
|
* Automatically re-fetches when `timeRange`, `origin`, or `page` changes.
|
|
*
|
|
* @param timeRange - Time-range preset that controls how far back to look.
|
|
* @param origin - Origin filter (default `"all"`).
|
|
* @returns Current data, pagination state, loading flag, and a `refresh`
|
|
* callback.
|
|
*/
|
|
export function useBans(
|
|
timeRange: TimeRange,
|
|
origin: BanOriginFilter = "all",
|
|
source: "fail2ban" | "archive" = "fail2ban",
|
|
): UseBansResult {
|
|
const [banItems, setBanItems] = useState<DashboardBanItem[]>([]);
|
|
const [total, setTotal] = useState<number>(0);
|
|
const [page, setPage] = useState<number>(1);
|
|
const [loading, setLoading] = useState<boolean>(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const abortRef = useRef<AbortController | null>(null);
|
|
|
|
// Reset page when time range, origin filter, or source changes.
|
|
useEffect(() => {
|
|
setPage(1);
|
|
}, [timeRange, origin, source]);
|
|
|
|
const doFetch = useCallback(async (): Promise<void> => {
|
|
abortRef.current?.abort();
|
|
const controller = new AbortController();
|
|
abortRef.current = controller;
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const data = await fetchBans(timeRange, page, PAGE_SIZE, origin, source, controller.signal);
|
|
if (controller.signal.aborted) return;
|
|
setBanItems(data.items);
|
|
setTotal(data.total);
|
|
} catch (err: unknown) {
|
|
if (controller.signal.aborted) return;
|
|
handleFetchError(err, setError, "Failed to fetch bans");
|
|
} finally {
|
|
if (!controller.signal.aborted) {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
}, [timeRange, page, origin, source]);
|
|
|
|
// Stable ref to the latest doFetch so the refresh callback is always current.
|
|
const doFetchRef = useRef(doFetch);
|
|
doFetchRef.current = doFetch;
|
|
|
|
useEffect(() => {
|
|
void doFetch();
|
|
return (): void => {
|
|
abortRef.current?.abort();
|
|
};
|
|
}, [doFetch]);
|
|
|
|
const refresh = useCallback((): void => {
|
|
void doFetchRef.current();
|
|
}, []);
|
|
|
|
return {
|
|
banItems,
|
|
total,
|
|
page,
|
|
setPage,
|
|
loading,
|
|
error,
|
|
refresh,
|
|
};
|
|
}
|