Files
BanGUI/Docs/Tasks.md

7.1 KiB
Raw Blame History

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:
      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:
      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.