Add jail distribution chart (Stage 5)

- backend: GET /api/dashboard/bans/by-jail endpoint
  - JailBanCount + BansByJailResponse Pydantic models in ban.py
  - bans_by_jail() service function with origin filter support
  - Route added to dashboard router
  - 17 new tests (7 service, 10 router); full suite 497 passed, 83% coverage

- frontend: JailDistributionChart component
  - JailBanCount / BansByJailResponse types in types/ban.ts
  - dashboardBansByJail endpoint constant in api/endpoints.ts
  - fetchBansByJail() in api/dashboard.ts
  - useJailDistribution hook in hooks/useJailDistribution.ts
  - JailDistributionChart component (horizontal bar chart, Recharts)
  - DashboardPage: full-width Jail Distribution section below Top Countries
This commit is contained in:
2026-03-11 17:01:19 +01:00
parent df0528b2c2
commit fe8eefa173
13 changed files with 799 additions and 6 deletions

View File

@@ -7,8 +7,9 @@
import { get } from "./client";
import { ENDPOINTS } from "./endpoints";
import type {
BanTrendResponse,
BanOriginFilter,
BansByJailResponse,
BanTrendResponse,
DashboardBanListResponse,
TimeRange,
} from "../types/ban";
@@ -72,3 +73,23 @@ export async function fetchBanTrend(
}
return get<BanTrendResponse>(`${ENDPOINTS.dashboardBansTrend}?${params.toString()}`);
}
/**
* Fetch ban counts aggregated by jail for the selected time window.
*
* @param range - Time-range preset: `"24h"`, `"7d"`, `"30d"`, or `"365d"`.
* @param origin - Origin filter: `"blocklist"`, `"selfblock"`, or `"all"`
* (default `"all"`, which omits the parameter entirely).
* @returns {@link BansByJailResponse} with jails sorted by ban count descending.
* @throws {ApiError} When the server returns a non-2xx status.
*/
export async function fetchBansByJail(
range: TimeRange,
origin: BanOriginFilter = "all",
): Promise<BansByJailResponse> {
const params = new URLSearchParams({ range });
if (origin !== "all") {
params.set("origin", origin);
}
return get<BansByJailResponse>(`${ENDPOINTS.dashboardBansByJail}?${params.toString()}`);
}

View File

@@ -31,6 +31,7 @@ export const ENDPOINTS = {
dashboardBans: "/dashboard/bans",
dashboardBansByCountry: "/dashboard/bans/by-country",
dashboardBansTrend: "/dashboard/bans/trend",
dashboardBansByJail: "/dashboard/bans/by-jail",
// -------------------------------------------------------------------------
// Jails