Files
BanGUI/Docs/Tasks.md

131 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `visibleBans` client-side filter in `MapPage.tsx` can remain as a defensive guard but must not be the only filter.
#### Implementation steps
1. **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.")` to `get_bans_by_country`.
- Pass it to `ban_service.bans_by_country(..., country_code=country_code)`.
2. **Backend — service** (`backend/app/services/ban_service.py`)
- Add `country_code: str | None = None` keyword argument to `bans_by_country`.
- After `geo_map` is built (existing geo-resolution step), collect IPs whose resolved country matches `country_code`.
- For the **fail2ban source**: call `fail2ban_db_repo.get_currently_banned` with `ip_filter=matched_ips` and no `limit` (remove the `_MAX_COMPANION_BANS` cap for filtered queries).
- For the **archive source**: filter `all_rows` to those whose IP is in `matched_ips` and return all of them (skip the `page_size=_MAX_COMPANION_BANS` call).
- When `country_code` is `None`, behaviour is identical to today.
3. **Backend — repository** (`backend/app/repositories/fail2ban_db_repo.py`)
- Add `ip_filter: list[str] | None = None` to `get_currently_banned`.
- When provided and non-empty, append `AND ip IN ({placeholders})` to the SQL `WHERE` clause, parameterised safely (never interpolated as a string).
4. **Backend — repository (archive)** (`backend/app/repositories/history_archive_repo.py`)
- Similarly add optional `ip_filter` to the archive companion-rows query used from `bans_by_country`.
5. **Frontend — API client** (`frontend/src/api/map.ts`)
- Add optional `countryCode?: string` parameter to `fetchBansByCountry`.
- When set, append `country_code=<value>` to the query string.
6. **Frontend — hook** (`frontend/src/hooks/useMapData.ts`)
- Add `countryCode?: string` to the function signature.
- Include it in the `useCallback` dependency array and pass it to `fetchBansByCountry`.
7. **Frontend — page** (`frontend/src/pages/MapPage.tsx`)
- Pass `selectedCountry ?? undefined` as `countryCode` to `useMapData`.
- The hook's effect will re-fetch automatically when `selectedCountry` changes; the existing `useEffect` that resets `page` to 1 already covers this.
#### 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_filter` SQL clause is parameterised and cannot be injected.
---
### TASK-002 — WorldMap: sticky table header and sticky pagination bar
**Priority:** Low
**Domain:** Frontend only
**References:** `Docs/Features.md §4`, `Docs/Web-Design.md`, `Docs/Web-Development.md`
#### Background
The companion ban table in `MapPage.tsx` is wrapped in `tableWrapper` (CSS `overflow: auto; maxHeight: 420px`). Both the Fluent UI `TableHeader` row and the `.pagination` div inside `tableWrapper` scroll with the content. Once the user scrolls more than a few rows, the column header labels disappear and the pagination controls become unreachable without scrolling back to the top or bottom.
#### Desired behaviour
- The column header row (`TableHeader →TableRow → TableHeaderCell × 6`) must remain fixed at the **top** of the scrollable container at all times.
- The pagination / page-size bar (`.pagination` div at the bottom of `tableWrapper`) must remain fixed at the **bottom** of the scrollable container at all times.
- Rows in `TableBody` scroll normally between the two fixed ends.
- No changes to the container height, overall layout, or other pages.
#### Implementation steps
All changes are in `frontend/src/pages/MapPage.tsx`.
1. **Sticky table header cells**
- In `useStyles` (`makeStyles`), add a new class:
```ts
stickyHeaderCell: {
position: "sticky",
top: 0,
zIndex: 1,
backgroundColor: tokens.colorNeutralBackground1,
boxShadow: `0 1px 0 ${tokens.colorNeutralStroke2}`,
},
```
- Apply `className={styles.stickyHeaderCell}` to **each** `TableHeaderCell` in the header row.
- Note: `position: sticky` on `<tr>` elements is unreliable across browsers for table layouts; apply it to each `<th>` (`TableHeaderCell`) instead.
2. **Sticky pagination bar**
- In the existing `pagination` entry in `useStyles`, add:
```ts
position: "sticky",
bottom: 0,
zIndex: 1,
```
- The existing `backgroundColor: tokens.colorNeutralBackground2` already prevents table rows from bleeding through.
3. **No other changes** — do not alter `tableWrapper`, its height, or anything outside `MapPage.tsx`.
#### Testing guidance
- Load the Map page with a time range that produces > 25 bans (enough to overflow the `420px` container).
- Scroll down through the table and confirm the column headers remain visible at the top.
- Scroll down and confirm the pagination bar remains visible at the bottom.
- Verify no visual artefacts (table body rows must not overlap or bleed through the sticky elements).
- Run `tsc --noEmit` — zero type errors expected.
- Run existing frontend tests: `vitest run` — no regressions.