# 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](Refactoring.md) and [Architekture.md](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:**

1. **Extend `WorldMapProps` and `GeoLayerProps`** in `WorldMap.tsx`:
   - Add `countryNames?: Record<string, string>` to `WorldMapProps` (optional — falls back to the ISO alpha-2 code when absent).
   - Thread it through `GeoLayer` the same way the threshold props are already threaded.

2. **Add hover state to `GeoLayer`** — declare:
   ```ts
   const [tooltip, setTooltip] = useState<{
     cc: string;
     count: number;
     name: string;
     x: number;
     y: number;
   } | null>(null);
   ```
   On each country `<g>` element add:
   - `onMouseEnter` — set `tooltip` with the country code, count, display name (from `countryNames`, falling back to the alpha-2 code), and mouse page coordinates (`e.clientX`, `e.clientY`).
   - `onMouseMove` — update only the `x`/`y` in the existing tooltip (keep name/count stable).
   - `onMouseLeave` — set `tooltip` to `null`.

   Skip setting the tooltip for countries where `cc === null` (no ISO mapping available) but keep `onMouseLeave` so re-entering after leaving from an unmapped border still clears the state.

3. **Render the tooltip inside `GeoLayer`** — because `GeoLayer` is rendered inside `ComposableMap` which is inside `mapWrapper`, 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 on `document.body` so it sits in the root stacking context and can be positioned with `position: fixed` using the raw `clientX`/`clientY` coordinates.

   Tooltip structure (styled with a new `makeStyles` class `tooltip` in `WorldMap.tsx`):
   ```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,
     )}
   ```

4. **Tooltip styles** — add three new classes to the `makeStyles` call in `WorldMap.tsx`:
   ```ts
   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,
   },
   ```

5. **Pass `countryNames` from `MapPage`** — in `MapPage.tsx`, add the `countryNames` prop to the existing `<WorldMap …>` JSX:
   ```tsx
   <WorldMap
     countries={countries}
     countryNames={countryNames}
     selectedCountry={selectedCountry}
     onSelectCountry={setSelectedCountry}
     …
   />
   ```

6. **Countries with zero bans** — the tooltip should still appear when the user hovers over a country with `0` bans (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 bans` or `0 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 --noEmit` produces 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.8`
> - `backend/pyproject.toml` — `0.9.4` ← **out of sync**
>
> Additionally 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.

1. Sync the two package manifests to the current release version:
   - Set `version` in `backend/pyproject.toml` to `0.9.8` (strip the leading `v` that `Docker/VERSION` contains).
   - `frontend/package.json` is already `0.9.8` — no change needed.
2. Make the backend read its version **directly from `Docker/VERSION`** at import time instead of from `pyproject.toml`, so a future release-script bump of `Docker/VERSION` is sufficient. Update `_read_pyproject_version()` in `backend/app/__init__.py`:
   - Add a new helper `_read_docker_version() -> str` that resolves `Docker/VERSION` relative to the repository root (two `parents` above `backend/app/`), strips the leading `v` and whitespace, and returns the bare semver string.
   - Change `_read_version()` to try `_read_docker_version()` first, then fall back to `_read_pyproject_version()`, then `importlib.metadata`.
3. Make the frontend read its version from `Docker/VERSION` at build time. In `frontend/vite.config.ts`, replace the `pkg.version` import with a `fs.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;` in `frontend/src/vite-env.d.ts` if the type declaration needs adjustment (it should not).

**Acceptance criteria:**
- `backend/app/__version__` equals the content of `Docker/VERSION` (without `v` prefix) at runtime.
- `frontend` build 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.

1. **`backend/app/models/server.py`** — Add to `ServerStatusResponse`:
   ```python
   bangui_version: str = Field(..., description="BanGUI application version.")
   ```
2. **`backend/app/models/config.py`** — Add to `ServiceStatusResponse`:
   ```python
   bangui_version: str = Field(..., description="BanGUI application version.")
   ```
3. **`backend/app/routers/dashboard.py`** — In `get_server_status`, import `__version__` from `app` and populate the new field:
   ```python
   return ServerStatusResponse(status=cached, bangui_version=__version__)
   ```
4. **`backend/app/routers/config.py`** — Do the same for the `GET /api/config/service-status` endpoint.

**Do not** change the existing `version` field (fail2ban daemon version) — keep it exactly as-is so nothing downstream breaks.

**Acceptance criteria:**
- `GET /api/dashboard/status` response JSON contains `"bangui_version": "0.9.8"`.
- `GET /api/config/service-status` response JSON contains `"bangui_version": "0.9.8"`.
- All existing backend tests pass.
- Add one test per endpoint asserting that `bangui_version` matches `app.__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.

1. **Type definitions**
   - `frontend/src/types/server.ts` — Add `bangui_version: string` to the `ServerStatusResponse` interface.
   - `frontend/src/types/config.ts` — Add `bangui_version: string` to the `ServiceStatusResponse` interface.

2. **Dashboard — `ServerStatusBar.tsx`**
   The status bar already renders `v{status.version}` (fail2ban version with a tooltip). Add a second badge directly adjacent to it that reads `BanGUI v{status.bangui_version}` with the tooltip `"BanGUI version"`. Match the existing badge style.

3. **Configuration → Server — `ServerHealthSection.tsx`**
   The health section already renders a `Version` row with the fail2ban version. Add a new row below it labelled `BanGUI` (or `BanGUI Version`) that renders `{status.bangui_version}`. Apply the same `statLabel` / `statValue` CSS classes used by the adjacent rows.

4. **Remove the duplicate from the sidebar** — Once the version is visible on the relevant pages, the sidebar footer in `frontend/src/layouts/MainLayout.tsx` can drop `v{__APP_VERSION__}` to avoid showing the version in three places. Replace it with the plain product name `BanGUI` — **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.8` with an appropriate tooltip.
- Configuration → Server health section shows a `BanGUI` version row reading `0.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)

---
