- Task 1: Mark imported blocklist IP addresses
- Add BanOrigin type and _derive_origin() to ban.py model
- Populate origin field in ban_service list_bans() and bans_by_country()
- BanTable and MapPage companion table show origin badge column
- Tests: origin derivation in test_ban_service.py and test_dashboard.py
- Task 2: Add origin filter to dashboard and world map
- ban_service: _origin_sql_filter() helper; origin param on list_bans()
and bans_by_country()
- dashboard router: optional origin query param forwarded to service
- Frontend: BanOriginFilter type + BAN_ORIGIN_FILTER_LABELS in ban.ts
- fetchBans / fetchBansByCountry forward origin to API
- useBans / useMapData accept and pass origin; page resets on change
- BanTable accepts origin prop; DashboardPage adds segmented filter
- MapPage adds origin Select next to time-range picker
- Tests: origin filter assertions in test_ban_service and test_dashboard
94 lines
2.7 KiB
TypeScript
94 lines
2.7 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 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",
|
|
): 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);
|
|
|
|
// Reset page when time range or origin filter changes.
|
|
useEffect(() => {
|
|
setPage(1);
|
|
}, [timeRange, origin]);
|
|
|
|
const doFetch = useCallback(async (): Promise<void> => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await fetchBans(timeRange, page, PAGE_SIZE, origin);
|
|
setBanItems(data.items);
|
|
setTotal(data.total);
|
|
} catch (err: unknown) {
|
|
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [timeRange, page, origin]);
|
|
|
|
// Stable ref to the latest doFetch so the refresh callback is always current.
|
|
const doFetchRef = useRef(doFetch);
|
|
doFetchRef.current = doFetch;
|
|
|
|
useEffect(() => {
|
|
void doFetch();
|
|
}, [doFetch]);
|
|
|
|
const refresh = useCallback((): void => {
|
|
void doFetchRef.current();
|
|
}, []);
|
|
|
|
return {
|
|
banItems,
|
|
total,
|
|
page,
|
|
setPage,
|
|
loading,
|
|
error,
|
|
refresh,
|
|
};
|
|
}
|