Add origin field and filter for ban sources (Tasks 1 & 2)
- 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
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBans } from "../api/dashboard";
|
||||
import type { DashboardBanItem, TimeRange } from "../types/ban";
|
||||
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
||||
|
||||
/** Items per page for the ban table. */
|
||||
const PAGE_SIZE = 100;
|
||||
@@ -33,29 +33,33 @@ export interface UseBansResult {
|
||||
/**
|
||||
* Fetch and manage dashboard ban-list data.
|
||||
*
|
||||
* Automatically re-fetches when `timeRange` or `page` changes.
|
||||
* 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): UseBansResult {
|
||||
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 changes.
|
||||
// Reset page when time range or origin filter changes.
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
}, [timeRange]);
|
||||
}, [timeRange, origin]);
|
||||
|
||||
const doFetch = useCallback(async (): Promise<void> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await fetchBans(timeRange, page, PAGE_SIZE);
|
||||
const data = await fetchBans(timeRange, page, PAGE_SIZE, origin);
|
||||
setBanItems(data.items);
|
||||
setTotal(data.total);
|
||||
} catch (err: unknown) {
|
||||
@@ -63,7 +67,7 @@ export function useBans(timeRange: TimeRange): UseBansResult {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [timeRange, page]);
|
||||
}, [timeRange, page, origin]);
|
||||
|
||||
// Stable ref to the latest doFetch so the refresh callback is always current.
|
||||
const doFetchRef = useRef(doFetch);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBansByCountry } from "../api/map";
|
||||
import type { BansByCountryResponse, MapBanItem, TimeRange } from "../types/map";
|
||||
import type { BanOriginFilter } from "../types/ban";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Return type
|
||||
@@ -31,7 +32,10 @@ export interface UseMapDataResult {
|
||||
// Hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function useMapData(range: TimeRange = "24h"): UseMapDataResult {
|
||||
export function useMapData(
|
||||
range: TimeRange = "24h",
|
||||
origin: BanOriginFilter = "all",
|
||||
): UseMapDataResult {
|
||||
const [data, setData] = useState<BansByCountryResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -43,7 +47,7 @@ export function useMapData(range: TimeRange = "24h"): UseMapDataResult {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchBansByCountry(range)
|
||||
fetchBansByCountry(range, origin)
|
||||
.then((resp) => {
|
||||
setData(resp);
|
||||
})
|
||||
@@ -55,7 +59,7 @@ export function useMapData(range: TimeRange = "24h"): UseMapDataResult {
|
||||
.finally((): void => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [range]);
|
||||
}, [range, origin]);
|
||||
|
||||
useEffect((): (() => void) => {
|
||||
load();
|
||||
|
||||
Reference in New Issue
Block a user