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:
2026-03-07 20:03:43 +01:00
parent 706d2e1df8
commit 53d664de4f
28 changed files with 1637 additions and 103 deletions

View File

@@ -13,6 +13,8 @@ import type {
JailConfigUpdate,
LogPreviewRequest,
LogPreviewResponse,
MapColorThresholdsResponse,
MapColorThresholdsUpdate,
RegexTestRequest,
RegexTestResponse,
ServerSettingsResponse,
@@ -119,3 +121,21 @@ export async function flushLogs(
);
return resp.message;
}
// ---------------------------------------------------------------------------
// Map color thresholds
// ---------------------------------------------------------------------------
export async function fetchMapColorThresholds(
): Promise<MapColorThresholdsResponse> {
return get<MapColorThresholdsResponse>(ENDPOINTS.configMapColorThresholds);
}
export async function updateMapColorThresholds(
update: MapColorThresholdsUpdate
): Promise<MapColorThresholdsResponse> {
return put<MapColorThresholdsResponse>(
ENDPOINTS.configMapColorThresholds,
update,
);
}

View File

@@ -6,7 +6,7 @@
import { get } from "./client";
import { ENDPOINTS } from "./endpoints";
import type { DashboardBanListResponse, TimeRange } from "../types/ban";
import type { DashboardBanListResponse, TimeRange, BanOriginFilter } from "../types/ban";
import type { ServerStatusResponse } from "../types/server";
/**
@@ -26,6 +26,8 @@ export async function fetchServerStatus(): Promise<ServerStatusResponse> {
* @param range - Time-range preset: `"24h"`, `"7d"`, `"30d"`, or `"365d"`.
* @param page - 1-based page number (default `1`).
* @param pageSize - Items per page (default `100`).
* @param origin - Origin filter: `"blocklist"`, `"selfblock"`, or `"all"`
* (default `"all"`, which omits the parameter entirely).
* @returns Paginated {@link DashboardBanListResponse}.
* @throws {ApiError} When the server returns a non-2xx status.
*/
@@ -33,12 +35,16 @@ export async function fetchBans(
range: TimeRange,
page = 1,
pageSize = 100,
origin: BanOriginFilter = "all",
): Promise<DashboardBanListResponse> {
const params = new URLSearchParams({
range,
page: String(page),
page_size: String(pageSize),
});
if (origin !== "all") {
params.set("origin", origin);
}
return get<DashboardBanListResponse>(`${ENDPOINTS.dashboardBans}?${params.toString()}`);
}

View File

@@ -65,6 +65,7 @@ export const ENDPOINTS = {
configReload: "/config/reload",
configRegexTest: "/config/regex-test",
configPreviewLog: "/config/preview-log",
configMapColorThresholds: "/config/map-color-thresholds",
// -------------------------------------------------------------------------
// Server settings

View File

@@ -5,15 +5,22 @@
import { get } from "./client";
import { ENDPOINTS } from "./endpoints";
import type { BansByCountryResponse, TimeRange } from "../types/map";
import type { BanOriginFilter } from "../types/ban";
/**
* Fetch ban counts aggregated by country for the given time window.
*
* @param range - Time-range preset.
* @param range - Time-range preset.
* @param origin - Origin filter: `"blocklist"`, `"selfblock"`, or `"all"`
* (default `"all"`, which omits the parameter entirely).
*/
export async function fetchBansByCountry(
range: TimeRange = "24h",
origin: BanOriginFilter = "all",
): Promise<BansByCountryResponse> {
const url = `${ENDPOINTS.dashboardBansByCountry}?range=${encodeURIComponent(range)}`;
return get<BansByCountryResponse>(url);
const params = new URLSearchParams({ range });
if (origin !== "all") {
params.set("origin", origin);
}
return get<BansByCountryResponse>(`${ENDPOINTS.dashboardBansByCountry}?${params.toString()}`);
}