12 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.
Open Issues
Architectural Review — 2026-03-16 The findings below were identified by auditing every backend and frontend module against the rules in Refactoring.md and Architekture.md. Tasks are grouped by layer and ordered so that lower-level fixes (repositories, services) are done before the layers that depend on them.
Feature: Worldmap Country Tooltip
2026-03-17 The world map on the Map page colours each country by ban count but provides no immediate information on hover — the user must click a country to see its name in the filter bar below, and must read the small SVG count label to learn the number of bans.
Goal: show a lightweight floating tooltip whenever the pointer enters a country, displaying the country's display name and its current ban count, so the information is accessible without a click.
Task WM-1 — Show country name and ban count tooltip on map hover
Scope: frontend/src/components/WorldMap.tsx, frontend/src/pages/MapPage.tsx
countryNames (ISO alpha-2 → display name) is already available in MapPage from useMapData but is not forwarded to WorldMap. The map component itself tracks no hover state. This task adds pointer-event handlers to each country <g> element, tracks the hovered country in local state together with the last known mouse coordinates, and renders a positionned HTML tooltip <div> on top of the SVG.
Implementation steps:
-
Extend
WorldMapPropsandGeoLayerPropsinWorldMap.tsx:- Add
countryNames?: Record<string, string>toWorldMapProps(optional — falls back to the ISO alpha-2 code when absent). - Thread it through
GeoLayerthe same way the threshold props are already threaded.
- Add
-
Add hover state to
GeoLayer— declare:const [tooltip, setTooltip] = useState<{ cc: string; count: number; name: string; x: number; y: number; } | null>(null);On each country
<g>element add:onMouseEnter— settooltipwith the country code, count, display name (fromcountryNames, falling back to the alpha-2 code), and mouse page coordinates (e.clientX,e.clientY).onMouseMove— update only thex/yin the existing tooltip (keep name/count stable).onMouseLeave— settooltiptonull.
Skip setting the tooltip for countries where
cc === null(no ISO mapping available) but keeponMouseLeaveso re-entering after leaving from an unmapped border still clears the state. -
Render the tooltip inside
GeoLayer— becauseGeoLayeris rendered insideComposableMapwhich is insidemapWrapper, the tooltip div cannot be positioned relative to the map wrapper from here (the SVG clip/transform would offset it). Instead, use a React portal (ReactDOM.createPortal) to mount the tooltip directly ondocument.bodyso it sits in the root stacking context and can be positioned withposition: fixedusing the rawclientX/clientYcoordinates.Tooltip structure (styled with a new
makeStylesclasstooltipinWorldMap.tsx):{tooltip && createPortal( <div className={styles.tooltip} style={{ left: tooltip.x + 12, top: tooltip.y + 12 }} role="tooltip" aria-live="polite" > <span className={styles.tooltipCountry}>{tooltip.name}</span> <span className={styles.tooltipCount}> {tooltip.count.toLocaleString()} ban{tooltip.count !== 1 ? "s" : ""} </span> </div>, document.body, )} -
Tooltip styles — add three new classes to the
makeStylescall inWorldMap.tsx:tooltip: { position: "fixed", zIndex: 9999, pointerEvents: "none", backgroundColor: tokens.colorNeutralBackground1, border: `1px solid ${tokens.colorNeutralStroke2}`, borderRadius: tokens.borderRadiusSmall, padding: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalS}`, display: "flex", flexDirection: "column", gap: tokens.spacingVerticalXXS, boxShadow: tokens.shadow4, }, tooltipCountry: { fontSize: tokens.fontSizeBase200, fontWeight: tokens.fontWeightSemibold, color: tokens.colorNeutralForeground1, }, tooltipCount: { fontSize: tokens.fontSizeBase200, color: tokens.colorNeutralForeground2, }, -
Pass
countryNamesfromMapPage— inMapPage.tsx, add thecountryNamesprop to the existing<WorldMap …>JSX:<WorldMap countries={countries} countryNames={countryNames} selectedCountry={selectedCountry} onSelectCountry={setSelectedCountry} … /> -
Countries with zero bans — the tooltip should still appear when the user hovers over a country with
0bans (showing the name and "0 bans"), so users know the country is tracked but has no bans. Do not suppress the tooltip for zero-count countries.
Acceptance criteria:
- Moving the pointer over any mapped country on the Map page shows a floating tooltip within 0 ms (synchronous state update) containing the country's full display name (e.g.
Germany) on the first line and the ban count (e.g.42 bansor0 bans) on the second line. - Moving the pointer off a country hides the tooltip immediately.
- The tooltip follows the pointer as it moves within a country's borders.
- Clicking a country still selects/deselects it exactly as before; the tooltip does not interfere with the click handler.
- The tooltip is not interactive (
pointerEvents: none) and does not steal focus from the map. tsc --noEmitproduces no new errors.
Status: ✅ Completed (2026-03-19)
Feature: Global Unique BanGUI Version
2026-03-17 The BanGUI application version is currently scattered across three independent files that are not kept in sync:
Docker/VERSION—v0.9.8(release artifact, written by the release script)frontend/package.json—0.9.8backend/pyproject.toml—0.9.4← out of syncAdditionally the BanGUI version is only shown in the sidebar footer (
MainLayout.tsx). Neither the Dashboard nor the Configuration → Server view exposes the BanGUI application version, only the fail2ban daemon version.Goal: one authoritative version string, propagated automatically to all layers, and displayed consistently on both the Dashboard and the Configuration → Server page.
Task GV-1 — Establish a single source of truth for the BanGUI version
Scope: Docker/VERSION, backend/pyproject.toml, frontend/package.json, backend/app/__init__.py
Docker/VERSION is already the file written by the release script (Docker/release.sh) and is therefore the natural single source of truth.
- Sync the two package manifests to the current release version:
- Set
versioninbackend/pyproject.tomlto0.9.8(strip the leadingvthatDocker/VERSIONcontains). frontend/package.jsonis already0.9.8— no change needed.
- Set
- Make the backend read its version directly from
Docker/VERSIONat import time instead of frompyproject.toml, so a future release-script bump ofDocker/VERSIONis sufficient. Update_read_pyproject_version()inbackend/app/__init__.py:- Add a new helper
_read_docker_version() -> strthat resolvesDocker/VERSIONrelative to the repository root (twoparentsabovebackend/app/), strips the leadingvand whitespace, and returns the bare semver string. - Change
_read_version()to try_read_docker_version()first, then fall back to_read_pyproject_version(), thenimportlib.metadata.
- Add a new helper
- Make the frontend read its version from
Docker/VERSIONat build time. Infrontend/vite.config.ts, replace thepkg.versionimport with afs.readFileSync('../Docker/VERSION', 'utf-8').trim().replace(/^v/, '')call so both the dev server and production build always reflect the file.- Update
declare const __APP_VERSION__: string;infrontend/src/vite-env.d.tsif the type declaration needs adjustment (it should not).
- Update
Acceptance criteria:
backend/app/__version__equals the content ofDocker/VERSION(withoutvprefix) at runtime.frontendbuild constant__APP_VERSION__equals the same value.- Bumping only
Docker/VERSION(e.g.v0.9.9) causes both layers to pick up the new version without touching any other file. - All existing tests pass (
pytest backend/).
Status: ✅ Completed (2026-03-19)
Task GV-2 — Expose the BanGUI version through the API
Scope: backend/app/models/server.py, backend/app/models/config.py, backend/app/routers/dashboard.py, backend/app/routers/config.py
Add a bangui_version field to every API response that already carries the fail2ban daemon version, so the frontend can display the BanGUI application version next to it.
backend/app/models/server.py— Add toServerStatusResponse:bangui_version: str = Field(..., description="BanGUI application version.")backend/app/models/config.py— Add toServiceStatusResponse:bangui_version: str = Field(..., description="BanGUI application version.")backend/app/routers/dashboard.py— Inget_server_status, import__version__fromappand populate the new field:return ServerStatusResponse(status=cached, bangui_version=__version__)backend/app/routers/config.py— Do the same for theGET /api/config/service-statusendpoint.
Do not change the existing version field (fail2ban daemon version) — keep it exactly as-is so nothing downstream breaks.
Acceptance criteria:
GET /api/dashboard/statusresponse JSON contains"bangui_version": "0.9.8".GET /api/config/service-statusresponse JSON contains"bangui_version": "0.9.8".- All existing backend tests pass.
- Add one test per endpoint asserting that
bangui_versionmatchesapp.__version__.
Status: ✅ Completed (2026-03-19)
Task GV-3 — Display the BanGUI version on Dashboard and Configuration → Server
Scope: frontend/src/components/ServerStatusBar.tsx, frontend/src/components/config/ServerHealthSection.tsx, frontend/src/types/server.ts, frontend/src/types/config.ts
After GV-2 the API delivers bangui_version; this task makes the frontend show it.
-
Type definitions
frontend/src/types/server.ts— Addbangui_version: stringto theServerStatusResponseinterface.frontend/src/types/config.ts— Addbangui_version: stringto theServiceStatusResponseinterface.
-
Dashboard —
ServerStatusBar.tsxThe status bar already rendersv{status.version}(fail2ban version with a tooltip). Add a second badge directly adjacent to it that readsBanGUI v{status.bangui_version}with the tooltip"BanGUI version". Match the existing badge style. -
Configuration → Server —
ServerHealthSection.tsxThe health section already renders aVersionrow with the fail2ban version. Add a new row below it labelledBanGUI(orBanGUI Version) that renders{status.bangui_version}. Apply the samestatLabel/statValueCSS classes used by the adjacent rows. -
Remove the duplicate from the sidebar — Once the version is visible on the relevant pages, the sidebar footer in
frontend/src/layouts/MainLayout.tsxcan dropv{__APP_VERSION__}to avoid showing the version in three places. Replace it with the plain product nameBanGUI— only do this if the design document (Docs/Web-Design.md) does not mandate showing the version there; otherwise leave it and note the decision in a comment.
Acceptance criteria:
- Dashboard status bar shows
BanGUI v0.9.8with an appropriate tooltip. - Configuration → Server health section shows a
BanGUIversion row reading0.9.8. - No TypeScript compile errors (
tsc --noEmit). - Both values originate from the same API field (
bangui_version) and therefore always match the backend version.
Status: ✅ Completed (2026-03-19)