Files
BanGUI/Docs/Tasks.md

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:

  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:

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

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

    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:

    <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/VERSIONv0.9.8 (release artifact, written by the release script)
  • frontend/package.json0.9.8
  • backend/pyproject.toml0.9.4out 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/).

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:
    bangui_version: str = Field(..., description="BanGUI application version.")
    
  2. backend/app/models/config.py — Add to ServiceStatusResponse:
    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:
    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__.

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