feat: Stage 4 — fail2ban connection and server status

This commit is contained in:
2026-02-28 21:48:03 +01:00
parent a41a99dad4
commit 60683da3ca
13 changed files with 1085 additions and 18 deletions

View File

@@ -106,29 +106,29 @@ With authentication working, this stage builds the persistent layout that every
---
## Stage 4 — fail2ban Connection & Server Status
## Stage 4 — fail2ban Connection & Server Status ✅ DONE
This stage establishes the live connection to the fail2ban daemon and surfaces its health to the user. It is a prerequisite for every data-driven feature.
### 4.1 Implement the health service
### 4.1 Implement the health service
Build `backend/app/services/health_service.py`. It connects to the fail2ban socket using the wrapper from Stage 1.8, sends a `status` command, and parses the response to extract: whether the server is reachable, the fail2ban version, the number of active jails, and aggregated ban/failure counts. Expose a method that returns a structured health status object. Log connectivity changes (online → offline and vice versa) via structlog. See [Features.md § 3 (Server Status Bar)](Features.md).
**Done.** `backend/app/services/health_service.py``probe(socket_path)` sends `ping`, `version`, `status`, and per-jail `status <jail>` commands via `Fail2BanClient`. Aggregates `Currently failed` and `Currently banned` across all jails. Returns `ServerStatus(online=True/False)`. `Fail2BanConnectionError` and `Fail2BanProtocolError` mapped to `online=False`. `_ok()` helper extracts payload from `(return_code, data)` tuples; `_to_dict()` normalises fail2ban's list-of-pairs format.
### 4.2 Implement the health-check background task
### 4.2 Implement the health-check background task
Create `backend/app/tasks/health_check.py`an APScheduler job that runs the health service probe every 30 seconds and caches the result in memory (e.g. on `app.state`). This ensures the dashboard endpoint can return fresh status without blocking on a socket call. See [Architekture.md § 2.2 (Tasks)](Architekture.md).
**Done.** `backend/app/tasks/health_check.py``register(app)` adds an APScheduler `interval` job that fires every 30 seconds (and immediately on startup via `next_run_time`). Result stored on `app.state.server_status`. `app.state.server_status` initialised to `ServerStatus(online=False)` as a safe placeholder. Wired into `main.py` lifespan after `scheduler.start()`.
### 4.3 Implement the dashboard status endpoint
### 4.3 Implement the dashboard status endpoint
Create `backend/app/routers/dashboard.py` with a `GET /api/dashboard/status` endpoint that returns the cached server status (online/offline, version, jail count, total bans, total failures). Define response models in `backend/app/models/server.py`. This endpoint is lightweight — it reads from the in-memory cache populated by the health-check task.
**Done.** `backend/app/routers/dashboard.py` `GET /api/dashboard/status` reads `app.state.server_status` (falls back to `ServerStatus(online=False)` when not yet set). Response model `ServerStatusResponse` from `backend/app/models/server.py` (pre-existing). Requires `AuthDep`. Registered in `create_app()`.
### 4.4 Build the server status bar component (frontend)
### 4.4 Build the server status bar component (frontend)
Create `frontend/src/components/ServerStatusBar.tsx`. This persistent bar appears at the top of the dashboard (and optionally on other pages). It displays the fail2ban connection status (green badge for online, red for offline), the server version, active jail count, and total bans/failures. Use Fluent UI `Badge` and `Text` components. Poll `GET /api/dashboard/status` at a reasonable interval or on page focus. Create `frontend/src/api/dashboard.ts`, `frontend/src/types/server.ts`, and a `useServerStatus` hook.
**Done.** `frontend/src/types/server.ts``ServerStatus` and `ServerStatusResponse` interfaces. `frontend/src/api/dashboard.ts``fetchServerStatus()`. `frontend/src/hooks/useServerStatus.ts``useServerStatus()` hook polling every 30 s and on window focus. `frontend/src/components/ServerStatusBar.tsx` Fluent UI v9 `Badge`, `Text`, `Spinner`, `Tooltip`; green/red badge for online/offline; version, jail count, bans, failures stats; refresh button. `DashboardPage.tsx` updated to render `<ServerStatusBar />` at the top.
### 4.5 Write tests for health service and dashboard
### 4.5 Write tests for health service and dashboard
Test that the health service correctly parses a mock fail2ban status response, handles socket errors gracefully, and that the dashboard endpoint returns the expected shape. Mock the fail2ban socket — tests must never touch a real daemon.
**Done.** 104 total tests pass (+19 new). `backend/tests/test_services/test_health_service.py` — 12 tests covering: online probe (version, jail count, ban/failure aggregation, empty jail list), connection error → offline, protocol error → offline, bad/error ping → offline, per-jail parse error tolerated, version failure tolerated. `backend/tests/test_routers/test_dashboard.py` — 6 tests covering: 200 when authenticated, 401 when unauthenticated, response shape, cached values returned, offline status, safe default when cache absent. fail2ban socket mocked via `unittest.mock.patch`. ruff 0 errors, mypy --strict 0 errors, tsc --noEmit 0 errors.
---