4.5 KiB
BanGUI — Task List
This document breaks the entire BanGUI project into development stages, ordered so that each stage builds on the previous one. Every task is described in prose with enough detail for a developer to begin work. References point to the relevant documentation.
Reference: Docs/Refactoring.md for full analysis of each issue.
Open Issues
TASK-001 — WorldMap: filter companion table by selected country (server-side)
Status: Done
Priority: Medium
Domain: Full-stack (backend + frontend)
References: Docs/Features.md §4, Docs/Web-Development.md
Background
The GET /api/dashboard/bans/by-country endpoint always returns the 200 most recent ban rows in bans (constant _MAX_COMPANION_BANS = 200 in backend/app/services/ban_service.py). MapPage.tsx stores a selectedCountry state and filters the returned rows client-side via visibleBans. This means the companion table can only show the fraction of a country's bans that fall within the global top-200 window. If the selected time range has, say, 1 500 bans and 300 are from China, but China's bans are not all in the top 200 overall, the table will silently display fewer than 300 rows.
When a country is selected the companion table must return the complete set of bans for that country so the user sees an accurate picture.
Desired behaviour
- No country selected → companion table shows the 200 most recent bans across all countries (existing behaviour, no change).
- Country selected → the server returns all ban entries for that country in the selected time window; no client-side row-count cap applies.
- Deselecting a country (clicking the same country again, or the "Clear filter" button) reverts to the default 200-row unfiltered view.
- The existing
visibleBansclient-side filter inMapPage.tsxcan remain as a defensive guard but must not be the only filter.
Implementation steps
-
Backend — router (
backend/app/routers/dashboard.py)- Add
country_code: str | None = Query(default=None, description="ISO alpha-2 country code to filter companion rows.")toget_bans_by_country. - Pass it to
ban_service.bans_by_country(..., country_code=country_code).
- Add
-
Backend — service (
backend/app/services/ban_service.py)- Add
country_code: str | None = Nonekeyword argument tobans_by_country. - After
geo_mapis built (existing geo-resolution step), collect IPs whose resolved country matchescountry_code. - For the fail2ban source: call
fail2ban_db_repo.get_currently_bannedwithip_filter=matched_ipsand nolimit(remove the_MAX_COMPANION_BANScap for filtered queries). - For the archive source: filter
all_rowsto those whose IP is inmatched_ipsand return all of them (skip thepage_size=_MAX_COMPANION_BANScall). - When
country_codeisNone, behaviour is identical to today.
- Add
-
Backend — repository (
backend/app/repositories/fail2ban_db_repo.py)- Add
ip_filter: list[str] | None = Nonetoget_currently_banned. - When provided and non-empty, append
AND ip IN ({placeholders})to the SQLWHEREclause, parameterised safely (never interpolated as a string).
- Add
-
Backend — repository (archive) (
backend/app/repositories/history_archive_repo.py)- Similarly add optional
ip_filterto the archive companion-rows query used frombans_by_country.
- Similarly add optional
-
Frontend — API client (
frontend/src/api/map.ts)- Add optional
countryCode?: stringparameter tofetchBansByCountry. - When set, append
country_code=<value>to the query string.
- Add optional
-
Frontend — hook (
frontend/src/hooks/useMapData.ts)- Add
countryCode?: stringto the function signature. - Include it in the
useCallbackdependency array and pass it tofetchBansByCountry.
- Add
-
Frontend — page (
frontend/src/pages/MapPage.tsx)- Pass
selectedCountry ?? undefinedascountryCodetouseMapData. - The hook's effect will re-fetch automatically when
selectedCountrychanges; the existinguseEffectthat resetspageto 1 already covers this.
- Pass
Testing guidance
- Select a country that has > 200 bans in the chosen time window; confirm the companion table shows more than the previous cap would allow.
- With no country selected, confirm only 200 rows are returned (no regression).
- Deselect the country; confirm the unfiltered 200-row view is restored.
- Test with the archive source as well as the fail2ban live source.
- Verify the
ip_filterSQL clause is parameterised and cannot be injected.