Add jail distribution chart (Stage 5)
- backend: GET /api/dashboard/bans/by-jail endpoint - JailBanCount + BansByJailResponse Pydantic models in ban.py - bans_by_jail() service function with origin filter support - Route added to dashboard router - 17 new tests (7 service, 10 router); full suite 497 passed, 83% coverage - frontend: JailDistributionChart component - JailBanCount / BansByJailResponse types in types/ban.ts - dashboardBansByJail endpoint constant in api/endpoints.ts - fetchBansByJail() in api/dashboard.ts - useJailDistribution hook in hooks/useJailDistribution.ts - JailDistributionChart component (horizontal bar chart, Recharts) - DashboardPage: full-width Jail Distribution section below Top Countries
This commit is contained in:
@@ -123,6 +123,7 @@ backend/
|
||||
│ │ └── import_log_repo.py # Import run history records
|
||||
│ ├── tasks/ # APScheduler background jobs
|
||||
│ │ ├── blocklist_import.py# Scheduled blocklist download and application
|
||||
│ │ ├── geo_cache_flush.py # Periodic geo cache persistence (dirty-set flush to SQLite)
|
||||
│ │ └── health_check.py # Periodic fail2ban connectivity probe
|
||||
│ └── utils/ # Helpers, constants, shared types
|
||||
│ ├── fail2ban_client.py # Async wrapper around the fail2ban socket protocol
|
||||
@@ -200,6 +201,7 @@ APScheduler background jobs that run on a schedule without user interaction.
|
||||
| Task | Purpose |
|
||||
|---|---|
|
||||
| `blocklist_import.py` | Downloads all enabled blocklist sources, validates entries, applies bans, records results in the import log |
|
||||
| `geo_cache_flush.py` | Periodically flushes newly resolved IPs from the in-memory dirty set to the `geo_cache` SQLite table (default: every 60 seconds). GET requests populate only the in-memory cache; this task persists them without blocking any request. |
|
||||
| `health_check.py` | Periodically pings the fail2ban socket and updates the cached server status so the frontend always has fresh data |
|
||||
|
||||
#### Utils (`app/utils/`)
|
||||
@@ -586,6 +588,7 @@ BanGUI maintains its **own SQLite database** (separate from the fail2ban databas
|
||||
|---|---|
|
||||
| `settings` | Key-value store for application configuration (master password hash, fail2ban socket path, database path, timezone, session duration) |
|
||||
| `sessions` | Active session tokens with expiry timestamps |
|
||||
| `geo_cache` | Resolved IP geolocation results (ip, country_code, country_name, asn, org, cached_at). Loaded into memory at startup via `load_cache_from_db()`; new entries are flushed back by the `geo_cache_flush` background task. |
|
||||
| `blocklist_sources` | Registered blocklist URLs (id, name, url, enabled, created_at, updated_at) |
|
||||
| `import_logs` | Record of every blocklist import run (id, source_id, timestamp, ips_imported, ips_skipped, errors, status) |
|
||||
|
||||
@@ -606,6 +609,8 @@ BanGUI maintains its **own SQLite database** (separate from the fail2ban databas
|
||||
- Session expiry is configurable (set during setup, stored in `settings`).
|
||||
- The frontend `AuthProvider` checks session validity on mount and redirects to `/login` if invalid.
|
||||
- The backend `dependencies.py` provides an `authenticated` dependency that validates the session cookie on every protected endpoint.
|
||||
- **Session validation cache** — validated session tokens are cached in memory for 10 seconds (`_session_cache` dict in `dependencies.py`) to avoid a SQLite round-trip on every request from the same browser. The cache is invalidated immediately on logout.
|
||||
- **Setup-completion flag** — once `is_setup_complete()` returns `True`, the result is stored in `app.state._setup_complete_cached`. The `SetupRedirectMiddleware` skips the DB query on all subsequent requests, removing 1 SQL query per request for the common post-setup case.
|
||||
|
||||
---
|
||||
|
||||
@@ -619,6 +624,7 @@ APScheduler 4.x (async mode) manages recurring background tasks.
|
||||
│ (async, in-process) │
|
||||
├──────────────────────┤
|
||||
│ blocklist_import │ ── runs on configured schedule (default: daily 03:00)
|
||||
│ geo_cache_flush │ ── runs every 60 seconds
|
||||
│ health_check │ ── runs every 30 seconds
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
@@ -353,7 +353,15 @@ Add the `BanTrendChart` to the dashboard page **above** the two country charts a
|
||||
|
||||
### Task 5.1 — Add a backend endpoint for ban counts per jail
|
||||
|
||||
**Status:** `not started`
|
||||
**Status:** `done`
|
||||
|
||||
Added `GET /api/dashboard/bans/by-jail`. New Pydantic models `JailBanCount` and
|
||||
`BansByJailResponse` added to `ban.py`. Service function `bans_by_jail()` in
|
||||
`ban_service.py` queries the `bans` table with `GROUP BY jail ORDER BY COUNT(*) DESC`
|
||||
and applies the origin filter. Route added to `dashboard.py`. 7 new service tests
|
||||
(happy path, total equality, empty DB, time-window exclusion, origin filter variants)
|
||||
and 10 new router tests — all pass, total suite 497 passed, 83% coverage.
|
||||
`ruff check` and `mypy --strict` pass.
|
||||
|
||||
The existing `GET /api/jails` endpoint returns jail metadata with `status.currently_banned` — but this counts **currently active** bans, not historical bans in the selected time window. The dashboard needs historical ban counts per jail within the selected time range.
|
||||
|
||||
@@ -395,7 +403,16 @@ class BansByJailResponse(BaseModel):
|
||||
|
||||
### Task 5.2 — Create the `JailDistributionChart` component
|
||||
|
||||
**Status:** `not started`
|
||||
**Status:** `done`
|
||||
|
||||
Created `frontend/src/components/JailDistributionChart.tsx` — a horizontal
|
||||
bar chart using Recharts `BarChart` showing ban counts per jail sorted descending.
|
||||
Added `JailBanCount`/`BansByJailResponse` types to `types/ban.ts`,
|
||||
`dashboardBansByJail` constant to `api/endpoints.ts`, `fetchBansByJail()` to
|
||||
`api/dashboard.ts`, and the `useJailDistribution` hook at
|
||||
`hooks/useJailDistribution.ts`. Component handles loading (Spinner), error
|
||||
(MessageBar), and empty states inline. `tsc --noEmit` and ESLint pass with zero
|
||||
warnings.
|
||||
|
||||
Create `frontend/src/components/JailDistributionChart.tsx`. This component renders a **horizontal bar chart** showing the distribution of bans across jails.
|
||||
|
||||
@@ -422,7 +439,14 @@ Create `frontend/src/components/JailDistributionChart.tsx`. This component rende
|
||||
|
||||
### Task 5.3 — Integrate the jail distribution chart into `DashboardPage`
|
||||
|
||||
**Status:** `not started`
|
||||
**Status:** `done`
|
||||
|
||||
Added a full-width "Jail Distribution" section card to `DashboardPage` below the
|
||||
"Top Countries" section (2-column country charts on row 1, jail chart full-width
|
||||
on row 2). The section renders `<JailDistributionChart timeRange={timeRange}
|
||||
origin={originFilter} />`, sharing the same state already used by the other
|
||||
charts. Loading, error, and empty states are handled inside
|
||||
`JailDistributionChart` itself. `tsc --noEmit` and ESLint pass with zero warnings.
|
||||
|
||||
Add the `JailDistributionChart` as a third chart card alongside the two country charts, or in a second chart row below them if space is constrained.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user