commit 460d877339442329815195aa9724cb2a8c90b76c Author: Lukas Date: Sat Feb 28 20:52:29 2026 +0100 instructions diff --git a/Docs/Architekture.md b/Docs/Architekture.md new file mode 100644 index 0000000..93310c5 --- /dev/null +++ b/Docs/Architekture.md @@ -0,0 +1,698 @@ +# BanGUI — Architecture + +This document describes the system architecture of BanGUI, a web application for monitoring, managing, and configuring fail2ban. It defines every major component, module, and data flow so that any developer can understand how the pieces fit together before writing code. + +--- + +## 1. High-Level Overview + +BanGUI is a two-tier web application with a clear separation between frontend and backend, connected through a RESTful JSON API. + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Browser │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ Frontend (React + Fluent UI) │ │ +│ │ TypeScript · Vite · Single-Page Application │ │ +│ └──────────────────────────┬─────────────────────────────────┘ │ +└─────────────────────────────┼────────────────────────────────────┘ + │ HTTP / JSON (REST API) +┌─────────────────────────────┼────────────────────────────────────┐ +│ Server │ +│ ┌──────────────────────────┴─────────────────────────────────┐ │ +│ │ Backend (FastAPI) │ │ +│ │ Python 3.12+ · Async · Pydantic v2 · structlog │ │ +│ └─────┬──────────────┬──────────────┬────────────────────────┘ │ +│ │ │ │ │ +│ ┌─────┴─────┐ ┌─────┴─────┐ ┌────┴─────┐ │ +│ │ SQLite │ │ fail2ban │ │ External │ │ +│ │ (App DB) │ │ (Socket) │ │ APIs │ │ +│ └───────────┘ └───────────┘ └──────────┘ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### Component Summary + +| Component | Technology | Purpose | +|---|---|---| +| **Frontend** | TypeScript, React, Fluent UI v9, Vite | User interface — displays data, captures user input, communicates with the backend API | +| **Backend** | Python 3.12+, FastAPI, Pydantic v2, aiosqlite | Business logic, data persistence, fail2ban communication, scheduling | +| **Application Database** | SQLite (via aiosqlite) | Stores BanGUI's own data: configuration, session state, blocklist sources, import logs | +| **fail2ban** | Unix domain socket | The monitored service — BanGUI reads status, issues commands, and reads the fail2ban database | +| **External APIs** | HTTP (via aiohttp) | IP geolocation, ASN/RIR lookups, blocklist downloads | + +--- + +## 2. Backend Architecture + +The backend follows a **layered architecture** with strict separation of concerns. Dependencies flow inward: routers depend on services, services depend on repositories — never the reverse. + +``` + ┌─────────────────────────────────┐ + │ FastAPI Application │ + │ (main.py) │ + └──────────┬──────────────────────-┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + ┌─────┴──────┐ ┌─────┴──────┐ ┌──────┴──────┐ + │ Routers │ │ Tasks │ │ Config │ + │ (HTTP) │ │ (Scheduled)│ │ (Settings) │ + └─────┬──────┘ └─────┬──────┘ └─────────────┘ + │ │ + ┌─────┴───────────────┴──────┐ + │ Services │ + │ (Business Logic) │ + └─────┬──────────────┬───────┘ + │ │ + ┌─────┴──────┐ ┌─────┴──────┐ + │Repositories│ │ External │ + │ (Database) │ │ Clients │ + └─────┬──────┘ └─────┬──────┘ + │ │ + ┌─────┴──────┐ ┌─────┴──────┐ + │ SQLite │ │fail2ban / │ + │ │ │HTTP APIs │ + └────────────┘ └────────────┘ +``` + +### 2.1 Project Structure + +``` +backend/ +├── app/ +│ ├── __init__.py +│ ├── main.py # FastAPI app factory, lifespan, exception handlers +│ ├── config.py # Pydantic settings (env vars, .env loading) +│ ├── dependencies.py # FastAPI Depends() providers (DB, services, auth) +│ ├── models/ # Pydantic schemas +│ │ ├── auth.py # Login request/response, session models +│ │ ├── ban.py # Ban request/response/domain models +│ │ ├── jail.py # Jail request/response/domain models +│ │ ├── config.py # Configuration view/edit models +│ │ ├── blocklist.py # Blocklist source/import models +│ │ ├── history.py # Ban history models +│ │ ├── server.py # Server status, health check models +│ │ └── setup.py # Setup wizard models +│ ├── routers/ # FastAPI routers (HTTP layer only) +│ │ ├── auth.py # POST /api/auth/login, POST /api/auth/logout +│ │ ├── setup.py # POST /api/setup (first-run configuration) +│ │ ├── dashboard.py # GET /api/dashboard/status, GET /api/dashboard/bans +│ │ ├── jails.py # CRUD + controls for jails +│ │ ├── bans.py # Ban/unban actions, currently banned list +│ │ ├── config.py # View/edit fail2ban configuration +│ │ ├── history.py # Historical ban queries +│ │ ├── blocklist.py # Blocklist source management, manual import trigger +│ │ ├── geo.py # IP geolocation and lookup +│ │ └── server.py # Server settings (log level, DB purge, etc.) +│ ├── services/ # Business logic (one service per domain) +│ │ ├── auth_service.py # Password verification, session creation/validation +│ │ ├── setup_service.py # First-run setup logic, configuration persistence +│ │ ├── jail_service.py # Jail listing, start/stop/reload, status aggregation +│ │ ├── ban_service.py # Ban/unban execution, currently-banned queries +│ │ ├── config_service.py # Read/write fail2ban config, regex validation +│ │ ├── history_service.py # Historical ban queries, per-IP timeline +│ │ ├── blocklist_service.py # Download, validate, apply blocklists +│ │ ├── geo_service.py # IP-to-country resolution, ASN/RIR lookup +│ │ ├── server_service.py # Server settings, log management, DB purge +│ │ └── health_service.py # fail2ban connectivity checks, version detection +│ ├── repositories/ # Data access layer (raw queries only) +│ │ ├── settings_repo.py # App configuration CRUD in SQLite +│ │ ├── session_repo.py # Session storage and lookup +│ │ ├── blocklist_repo.py # Blocklist sources and import log persistence +│ │ └── import_log_repo.py # Import run history records +│ ├── tasks/ # APScheduler background jobs +│ │ ├── blocklist_import.py# Scheduled blocklist download and application +│ │ └── health_check.py # Periodic fail2ban connectivity probe +│ └── utils/ # Helpers, constants, shared types +│ ├── fail2ban_client.py # Async wrapper around the fail2ban socket protocol +│ ├── ip_utils.py # IP/CIDR validation and normalisation +│ ├── time_utils.py # Timezone-aware datetime helpers +│ └── constants.py # Shared constants (default paths, limits, etc.) +├── tests/ +│ ├── conftest.py # Shared fixtures (test app, client, mock DB) +│ ├── test_routers/ # One test file per router +│ ├── test_services/ # One test file per service +│ └── test_repositories/ # One test file per repository +├── pyproject.toml +└── .env.example +``` + +### 2.2 Module Purposes + +#### Routers (`app/routers/`) + +The HTTP interface layer. Each router maps URL paths to handler functions. Routers parse and validate incoming requests using Pydantic models, delegate all logic to services, and return typed responses. They contain **zero business logic**. + +| Router | Prefix | Purpose | +|---|---|---| +| `auth.py` | `/api/auth` | Login (password check), logout, session validation | +| `setup.py` | `/api/setup` | First-run wizard — save initial configuration | +| `dashboard.py` | `/api/dashboard` | Server status bar data, recent bans for the dashboard | +| `jails.py` | `/api/jails` | List jails, jail detail, start/stop/reload/idle controls | +| `bans.py` | `/api/bans` | Ban an IP, unban an IP, unban all, list currently banned IPs | +| `config.py` | `/api/config` | Read and write fail2ban jail/filter/server configuration | +| `history.py` | `/api/history` | Query historical bans, per-IP timeline | +| `blocklist.py` | `/api/blocklists` | CRUD blocklist sources, trigger import, view import logs | +| `geo.py` | `/api/geo` | IP geolocation lookup, ASN and RIR data | +| `server.py` | `/api/server` | Log level, log target, DB path, purge age, flush logs | + +#### Services (`app/services/`) + +The business logic layer. Services orchestrate operations, enforce rules, and coordinate between repositories, the fail2ban client, and external APIs. Each service covers a single domain. + +| Service | Purpose | +|---|---| +| `auth_service.py` | Hashes and verifies the master password, creates and validates session tokens, enforces session expiry | +| `setup_service.py` | Validates setup input, persists initial configuration, ensures setup runs only once | +| `jail_service.py` | Retrieves jail list and details from fail2ban, aggregates metrics (banned count, failure count), sends start/stop/reload/idle commands | +| `ban_service.py` | Executes ban and unban commands via the fail2ban socket, queries the currently banned IP list, validates IPs before banning | +| `config_service.py` | Reads active jail and filter configuration from fail2ban, writes configuration changes, validates regex patterns, triggers reload | +| `history_service.py` | Queries the fail2ban database for historical ban records, builds per-IP timelines, computes ban counts and repeat-offender flags | +| `blocklist_service.py` | Downloads blocklists via aiohttp, validates IPs/CIDRs, applies bans through fail2ban or iptables, logs import results | +| `geo_service.py` | Resolves IP addresses to country, ASN, and RIR using external APIs or a local database, caches results | +| `server_service.py` | Reads and writes fail2ban server-level settings (log level, log target, syslog socket, DB location, purge age) | +| `health_service.py` | Probes fail2ban socket connectivity, retrieves server version and global stats, reports online/offline status | + +#### Repositories (`app/repositories/`) + +The data access layer. Repositories execute raw SQL queries against the application SQLite database. They return plain data or domain models — they never raise HTTP exceptions or contain business logic. + +| Repository | Purpose | +|---|---| +| `settings_repo.py` | CRUD operations for application settings (master password hash, DB path, fail2ban socket path, preferences) | +| `session_repo.py` | Store, retrieve, and delete session records for authentication | +| `blocklist_repo.py` | Persist blocklist source definitions (name, URL, enabled/disabled) | +| `import_log_repo.py` | Record import run results (timestamp, source, IPs imported, errors) for the import log view | + +#### Models (`app/models/`) + +Pydantic schemas that define data shapes and validation. Models are split into three categories per domain: + +- **Request models** — validate incoming API data (e.g., `BanRequest`, `LoginRequest`) +- **Response models** — shape outgoing API data (e.g., `JailResponse`, `BanListResponse`) +- **Domain models** — internal representations used between services and repositories (e.g., `Ban`, `Jail`) + +#### Tasks (`app/tasks/`) + +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 | +| `health_check.py` | Periodically pings the fail2ban socket and updates the cached server status so the frontend always has fresh data | + +#### Utils (`app/utils/`) + +Pure helper modules with no framework dependencies. + +| Module | Purpose | +|---|---| +| `fail2ban_client.py` | Async client that communicates with fail2ban via its Unix domain socket — sends commands and parses responses using the fail2ban protocol. Modelled after [`./fail2ban-master/fail2ban/client/csocket.py`](../fail2ban-master/fail2ban/client/csocket.py) and [`./fail2ban-master/fail2ban/client/fail2banclient.py`](../fail2ban-master/fail2ban/client/fail2banclient.py). | +| `ip_utils.py` | Validates IPv4/IPv6 addresses and CIDR ranges using the `ipaddress` stdlib module, normalises formats | +| `time_utils.py` | Timezone-aware datetime construction, formatting helpers, time-range calculations | +| `constants.py` | Shared constants: default socket path, default database path, time-range presets, limits | + +#### Configuration (`app/config.py`) + +A single Pydantic settings model that loads all configuration from environment variables (prefixed `BANGUI_`) and an optional `.env` file. Validated at startup — the application refuses to start if required values are missing. + +#### Dependencies (`app/dependencies.py`) + +FastAPI `Depends()` providers that inject shared resources into route handlers: the database connection, service instances, the authenticated session, and the fail2ban client. This is the wiring layer that connects routers to services without tight coupling. + +#### Application Entry Point (`app/main.py`) + +The FastAPI app factory. Responsibilities: + +- Creates the `FastAPI` instance with metadata (title, version, docs URL) +- Registers the **lifespan** context manager (startup: open DB, create aiohttp session, start scheduler; shutdown: close all) +- Mounts all routers +- Registers global exception handlers that map domain exceptions to HTTP status codes +- Applies the setup-redirect middleware (redirects all requests to `/api/setup` when no configuration exists) + +--- + +## 3. Frontend Architecture + +The frontend is a React single-page application built with TypeScript, Vite, and Fluent UI v9. It communicates exclusively with the backend REST API — it never accesses fail2ban, the database, or external services directly. + +``` +┌──────────────────────────────────────────────────────────────┐ +│ React Application │ +│ │ +│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │ +│ │ Pages │───▶│ Components │───▶│ Fluent UI v9 │ │ +│ └────┬─────┘ └────────────┘ └──────────────────┘ │ +│ │ │ +│ ┌────┴─────┐ ┌────────────┐ ┌──────────────────┐ │ +│ │ Hooks │───▶│ API Layer │───▶│ Backend (REST) │ │ +│ └──────────┘ └────────────┘ └──────────────────┘ │ +│ │ +│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │ +│ │Providers │ │ Types │ │ Theme │ │ +│ │(Context) │ │(Interfaces)│ │(Tokens, Styles) │ │ +│ └──────────┘ └────────────┘ └──────────────────┘ │ +└──────────────────────────────────────────────────────────────┘ +``` + +### 3.1 Project Structure + +``` +frontend/ +├── public/ +├── src/ +│ ├── api/ # API client and per-domain request functions +│ │ ├── client.ts # Central fetch wrapper (typed GET/POST/PUT/DELETE) +│ │ ├── endpoints.ts # API path constants +│ │ ├── auth.ts # Login, logout, session check +│ │ ├── dashboard.ts # Dashboard status and ban list +│ │ ├── jails.ts # Jail CRUD and controls +│ │ ├── bans.ts # Ban/unban actions, banned list +│ │ ├── config.ts # Configuration read/write +│ │ ├── history.ts # Ban history queries +│ │ ├── blocklist.ts # Blocklist source management +│ │ ├── geo.ts # IP lookup / geolocation +│ │ └── server.ts # Server settings +│ ├── assets/ # Static images, fonts, icons +│ ├── components/ # Reusable UI components +│ │ ├── BanTable.tsx # Data table for ban entries +│ │ ├── JailCard.tsx # Summary card for a jail +│ │ ├── StatusBar.tsx # Server status indicator strip +│ │ ├── TimeRangeSelector.tsx # Quick preset picker (24h, 7d, 30d, 365d) +│ │ ├── IpInput.tsx # IP address input with validation +│ │ ├── RegexTester.tsx # Side-by-side regex match preview +│ │ ├── WorldMap.tsx # Country-outline map with ban counts +│ │ ├── ImportLogTable.tsx # Blocklist import run history +│ │ ├── ConfirmDialog.tsx # Reusable confirmation modal +│ │ └── ... # (additional shared components) +│ ├── hooks/ # Custom React hooks (stateful logic + API calls) +│ │ ├── useAuth.ts # Login state, login/logout actions +│ │ ├── useBans.ts # Fetch ban list for a time range +│ │ ├── useJails.ts # Fetch jail list and details +│ │ ├── useConfig.ts # Fetch and update configuration +│ │ ├── useHistory.ts # Fetch historical ban data +│ │ ├── useBlocklists.ts # Fetch and manage blocklist sources +│ │ ├── useServerStatus.ts # Poll server health / status +│ │ └── useGeo.ts # IP lookup hook +│ ├── layouts/ # Page-level layout wrappers +│ │ └── AppLayout.tsx # Sidebar navigation + header + content area +│ ├── pages/ # Route-level page components (one per route) +│ │ ├── SetupPage.tsx # First-run wizard +│ │ ├── LoginPage.tsx # Password prompt +│ │ ├── DashboardPage.tsx # Ban overview, status bar, access list +│ │ ├── WorldMapPage.tsx # Geographical ban map + access table +│ │ ├── JailsPage.tsx # Jail list, detail, controls, ban/unban +│ │ ├── ConfigPage.tsx # Configuration viewer/editor +│ │ ├── HistoryPage.tsx # Ban history browser +│ │ └── BlocklistPage.tsx # Blocklist source management + import log +│ ├── providers/ # React context providers +│ │ ├── AuthProvider.tsx # Authentication state and guards +│ │ └── ThemeProvider.tsx # Light/dark theme switching +│ ├── theme/ # Fluent UI theme definitions +│ │ ├── customTheme.ts # Brand colour ramp, light and dark themes +│ │ └── tokens.ts # Spacing, sizing, and z-index constants +│ ├── types/ # Shared TypeScript interfaces +│ │ ├── auth.ts # LoginRequest, SessionInfo +│ │ ├── ban.ts # Ban, BanListResponse, BanRequest +│ │ ├── jail.ts # Jail, JailDetail, JailListResponse +│ │ ├── config.ts # ConfigSection, ConfigUpdateRequest +│ │ ├── history.ts # HistoryEntry, IpTimeline +│ │ ├── blocklist.ts # BlocklistSource, ImportLogEntry +│ │ ├── geo.ts # GeoInfo, AsnInfo +│ │ ├── server.ts # ServerStatus, ServerSettings +│ │ └── api.ts # ApiError, PaginatedResponse +│ ├── utils/ # Pure helper functions +│ │ ├── formatDate.ts # Date/time formatting with timezone support +│ │ ├── formatIp.ts # IP display formatting +│ │ └── constants.ts # Frontend constants (time presets, etc.) +│ ├── App.tsx # Root: FluentProvider + BrowserRouter + routes +│ ├── main.tsx # Vite entry point +│ └── vite-env.d.ts # Vite type shims +├── tsconfig.json +├── vite.config.ts +└── package.json +``` + +### 3.2 Module Purposes + +#### Pages (`src/pages/`) + +Top-level route components. Each page composes layout, components, and hooks to create a full screen. Pages contain **no business logic** — they orchestrate what is displayed and delegate data fetching to hooks. + +| Page | Route | Purpose | +|---|---|---| +| `SetupPage` | `/setup` | First-run wizard: set master password, database path, fail2ban connection, preferences | +| `LoginPage` | `/login` | Single-field password prompt; redirects to requested page after success | +| `DashboardPage` | `/` | Server status bar, ban list table, access list tab, time-range selector | +| `WorldMapPage` | `/map` | World map with per-country ban counts, companion access table, country filter | +| `JailsPage` | `/jails` | Jail overview list, jail detail panel, controls (start/stop/reload), ban/unban forms, IP lookup, whitelist management | +| `ConfigPage` | `/config` | View and edit jail parameters, filter regex, server settings, regex tester, add log observation | +| `HistoryPage` | `/history` | Browse all past bans, filter by jail/IP/time, per-IP timeline drill-down | +| `BlocklistPage` | `/blocklists` | Manage blocklist sources, schedule configuration, import log, manual import trigger | + +#### Components (`src/components/`) + +Reusable UI building blocks. Components receive data via props, emit changes via callbacks, and never call the API directly. Built exclusively with Fluent UI v9 components. + +| Component | Purpose | +|---|---| +| `StatusBar` | Displays fail2ban server status (online/offline, version, jail count, total bans) | +| `BanTable` | Sortable data table for ban entries with columns for time, IP, jail, country, etc. | +| `JailCard` | Summary card showing jail name, status badge, key metrics | +| `TimeRangeSelector` | Quick-preset picker for filtering data (24h, 7d, 30d, 365d) | +| `IpInput` | IP address text field with inline validation | +| `WorldMap` | SVG/Canvas country-outline map with count overlays and click-to-filter | +| `RegexTester` | Side-by-side sample log + regex input with live match highlighting | +| `ImportLogTable` | Table displaying blocklist import history | +| `ConfirmDialog` | Reusable Fluent UI Dialog for destructive action confirmations | + +#### Hooks (`src/hooks/`) + +Encapsulate all stateful logic, side effects, and API calls. Components and pages consume hooks to stay declarative. + +| Hook | Purpose | +|---|---| +| `useAuth` | Manages login state, provides `login()`, `logout()`, and `isAuthenticated` | +| `useBans` | Fetches ban list for a given time range, returns `{ bans, loading, error }` | +| `useJails` | Fetches jail list and individual jail detail | +| `useConfig` | Reads and writes fail2ban configuration | +| `useHistory` | Queries historical ban data with filters | +| `useBlocklists` | Manages blocklist sources and import triggers | +| `useServerStatus` | Polls the server status endpoint at an interval | +| `useGeo` | Performs IP geolocation lookups on demand | + +#### API Layer (`src/api/`) + +A thin typed wrapper around `fetch`. All HTTP communication is centralised here — components and hooks never construct HTTP requests directly. + +| Module | Purpose | +|---|---| +| `client.ts` | Central `get`, `post`, `put`, `del` functions with error handling and credentials | +| `endpoints.ts` | All API path constants in one place — no hard-coded URLs anywhere else | +| `auth.ts` | `login()`, `logout()`, `checkSession()` | +| `dashboard.ts` | `fetchStatus()`, `fetchRecentBans()` | +| `jails.ts` | `fetchJails()`, `fetchJailDetail()`, `startJail()`, `stopJail()`, `reloadJail()` | +| `bans.ts` | `banIp()`, `unbanIp()`, `unbanAll()`, `fetchBannedIps()` | +| `config.ts` | `fetchConfig()`, `updateConfig()`, `testRegex()` | +| `history.ts` | `fetchHistory()`, `fetchIpTimeline()` | +| `blocklist.ts` | `fetchSources()`, `addSource()`, `removeSource()`, `triggerImport()`, `fetchImportLog()` | +| `geo.ts` | `lookupIp()` | +| `server.ts` | `fetchServerSettings()`, `updateServerSettings()` | + +#### Types (`src/types/`) + +Shared TypeScript interfaces and type aliases. Purely declarative — no runtime code. Grouped by domain. Any type used by two or more files lives here. + +#### Providers (`src/providers/`) + +React context providers for application-wide concerns. + +| Provider | Purpose | +|---|---| +| `AuthProvider` | Holds authentication state, wraps protected routes, redirects unauthenticated users to `/login` | +| `ThemeProvider` | Manages light/dark theme selection, supplies the active Fluent UI theme to `FluentProvider` | + +#### Theme (`src/theme/`) + +Fluent UI custom theme definitions and design token constants. No component logic — only colours, spacing, and sizing values. + +#### Utils (`src/utils/`) + +Pure helper functions with no React or framework dependency. Date formatting, IP display formatting, shared constants. + +--- + +## 4. Data Flow + +### 4.1 Request Lifecycle + +Every user action follows this flow through the system: + +``` +User Action (click, form submit) + │ + ▼ + Page / Component + │ calls hook + ▼ + Hook (useXxx) + │ calls API function + ▼ + API Layer (src/api/) + │ HTTP request + ▼ + FastAPI Router (app/routers/) + │ validates input (Pydantic) + │ calls Depends() for auth + services + ▼ + Service (app/services/) + │ enforces business rules + │ calls repository or fail2ban client + ▼ + Repository (app/repositories/) or fail2ban Client (app/utils/) + │ executes SQL query │ sends socket command + ▼ ▼ + SQLite Database fail2ban Server + │ │ + └──────────── response bubbles back up ─────┘ +``` + +### 4.2 Authentication Flow + +``` +┌─────────┐ POST /api/auth/login ┌─────────────┐ +│ Login │ ─────────────────────────────▶│ auth router │ +│ Page │ { password: "***" } │ │ +└─────────┘ └──────┬───────┘ + │ + ┌──────┴───────┐ + │ auth_service │ + │ - verify hash │ + │ - create token│ + └──────┬───────┘ + │ + ┌──────┴───────┐ + │ session_repo │ + │ - store token │ + └──────┬───────┘ + │ + Set-Cookie: session= │ +◀─────────────────────────────────────────────────┘ +``` + +- The master password is hashed and stored during setup. +- On login, the submitted password is verified against the stored hash. +- A session token is created, stored in the database, and returned as an HTTP-only cookie. +- Every subsequent request is authenticated via the session cookie using a FastAPI dependency. +- The `AuthProvider` on the frontend guards all routes except `/setup` and `/login`. + +### 4.3 fail2ban Communication + +BanGUI communicates with fail2ban through its **Unix domain socket** using the fail2ban client-server protocol. + +``` +┌────────────────────┐ ┌──────────────────┐ +│ ban_service.py │ │ fail2ban server │ +│ jail_service.py │──socket──│ │ +│ config_service.py │ │ /var/run/fail2ban│ +│ health_service.py │ │ /fail2ban.sock │ +└────────────────────┘ └──────────────────┘ +``` + +The `fail2ban_client.py` utility module wraps this communication: + +- Opens an async connection to the Unix socket +- Serialises commands using the fail2ban protocol (pickle-based, see [`./fail2ban-master/fail2ban/client/csocket.py`](../fail2ban-master/fail2ban/client/csocket.py)) +- Parses responses into typed Python objects +- Handles connection errors gracefully (timeout, socket not found, permission denied) + +> **Reference source:** The vendored fail2ban source at [`./fail2ban-master`](../fail2ban-master) is included in the repository as an authoritative protocol reference. When implementing or debugging socket communication, consult: +> +> | File | What it documents | +> |---|---| +> | [`./fail2ban-master/fail2ban/client/csocket.py`](../fail2ban-master/fail2ban/client/csocket.py) | `CSocket` class — low-level Unix socket connection, pickle serialisation, `CSPROTO.END` framing | +> | [`./fail2ban-master/fail2ban/client/fail2banclient.py`](../fail2ban-master/fail2ban/client/fail2banclient.py) | `Fail2banClient` — command dispatch, argument handling, response beautification | +> | [`./fail2ban-master/fail2ban/client/beautifier.py`](../fail2ban-master/fail2ban/client/beautifier.py) | Response parser — converts raw server replies into human-readable / structured output | +> | [`./fail2ban-master/fail2ban/protocol.py`](../fail2ban-master/fail2ban/protocol.py) | `CSPROTO` constants and the full list of supported commands with descriptions | +> | [`./fail2ban-master/fail2ban/client/configreader.py`](../fail2ban-master/fail2ban/client/configreader.py) | Config file parsing used by fail2ban — reference for understanding jail/filter structure | + +**Key commands used:** + +| Command | Purpose | +|---|---| +| `status` | Get global server status (number of jails, fail2ban version) | +| `status ` | Get jail detail (banned IPs, failure count, filter info) | +| `set banip ` | Ban an IP in a specific jail | +| `set unbanip ` | Unban an IP from a specific jail | +| `set idle on/off` | Toggle jail idle mode | +| `start/stop ` | Start or stop a jail | +| `reload ` | Reload a single jail configuration | +| `reload` | Reload all jails | +| `get ...` | Read jail settings (findtime, bantime, maxretry, filter, actions, etc.) | +| `set ...` | Write jail settings | +| `set loglevel ` | Change server log level | +| `set logtarget ` | Change server log target | +| `set dbpurgeage ` | Set database purge age | +| `flushlogs` | Flush and re-open log files | + +### 4.4 fail2ban Database Access + +In addition to the live socket, BanGUI reads the **fail2ban SQLite database** directly for historical data that the socket protocol does not expose (ban history, past log matches). This is read-only access. + +``` +history_service.py ──read-only──▶ fail2ban.db (SQLite) +``` + +The fail2ban database contains: + +- `bans` table — historical ban records (IP, jail, timestamp, ban data) +- `jails` table — jail definitions +- `logs` table — matched log lines per ban + +BanGUI queries these tables to power the Ban History page and the per-IP timeline view. + +### 4.5 External API Communication + +``` +geo_service.py ──aiohttp──▶ IP Geolocation API (country, ASN, RIR) +blocklist_service.py ──aiohttp──▶ Blocklist URLs (plain-text IP lists) +``` + +All external HTTP calls go through a shared `aiohttp.ClientSession` created during startup and closed during shutdown. External data is validated before use (IP format, response structure). + +--- + +## 5. Database Design + +BanGUI maintains its **own SQLite database** (separate from the fail2ban database) to store application state. + +### 5.1 Application Database Tables + +| Table | Purpose | +|---|---| +| `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 | +| `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) | + +### 5.2 Database Boundaries + +| Database | Owner | BanGUI Access | +|---|---|---| +| BanGUI application DB (`bangui.db`) | BanGUI | Read + Write | +| fail2ban DB (`fail2ban.db`) | fail2ban | Read-only (for history queries) | + +--- + +## 6. Authentication & Session Management + +- **Single-user model** — one master password, no usernames. +- Password is hashed with a strong algorithm (e.g., bcrypt or argon2) and stored in the application database during setup. +- Sessions are token-based, stored server-side in the `sessions` table, and delivered to the browser as HTTP-only secure cookies. +- 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. + +--- + +## 7. Scheduling + +APScheduler 4.x (async mode) manages recurring background tasks. + +``` +┌──────────────────────┐ +│ APScheduler │ +│ (async, in-process) │ +├──────────────────────┤ +│ blocklist_import │ ── runs on configured schedule (default: daily 03:00) +│ health_check │ ── runs every 30 seconds +└──────────────────────┘ +``` + +- The scheduler is started during the FastAPI lifespan startup and stopped during shutdown. +- Job schedules are persisted in the application database so they survive restarts. +- Users can modify the blocklist import schedule through the web interface. +- A manual "Run Now" button triggers the blocklist import job outside the schedule. + +--- + +## 8. API Design + +### 8.1 Conventions + +- All endpoints are grouped under `/api/` prefix. +- JSON request and response bodies, validated by Pydantic models. +- Authentication via session cookie on all endpoints except `/api/setup` and `/api/auth/login`. +- Setup-redirect middleware: while no configuration exists, all endpoints return `303 See Other` → `/api/setup`. +- Standard HTTP status codes: `200` success, `201` created, `204` no content, `400` bad request, `401` unauthorized, `404` not found, `422` validation error, `500` server error. +- Error responses follow a consistent shape: `{ "detail": "Human-readable message" }`. + +### 8.2 Endpoint Groups + +| Group | Endpoints | Description | +|---|---|---| +| **Auth** | `POST /login`, `POST /logout` | Session management | +| **Setup** | `POST /setup` | First-run configuration | +| **Dashboard** | `GET /status`, `GET /bans` | Overview data for the main page | +| **Jails** | `GET /`, `GET /:name`, `POST /:name/start`, `POST /:name/stop`, `POST /:name/reload`, `POST /reload-all` | Jail listing and controls | +| **Bans** | `POST /ban`, `POST /unban`, `POST /unban-all`, `GET /banned` | Ban management | +| **Config** | `GET /`, `PUT /`, `POST /test-regex` | Configuration viewing and editing | +| **History** | `GET /`, `GET /ip/:ip` | Historical ban browsing | +| **Blocklists** | `GET /sources`, `POST /sources`, `DELETE /sources/:id`, `POST /import`, `GET /import-log` | Blocklist management | +| **Geo** | `GET /lookup/:ip` | IP geolocation and enrichment | +| **Server** | `GET /settings`, `PUT /settings`, `POST /flush-logs` | Server-level settings | + +--- + +## 9. Deployment Architecture + +``` +┌──────────────────────────────────────────────────┐ +│ Host Machine │ +│ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ Reverse Proxy (nginx / caddy) │ │ +│ │ - TLS termination │ │ +│ │ - /api/* → backend (uvicorn) │ │ +│ │ - /* → frontend (static files) │ │ +│ └──────────────┬───────────────┬──────────────┘ │ +│ │ │ │ +│ ┌──────────────┴───┐ ┌───────┴──────────────┐ │ +│ │ Backend │ │ Frontend │ │ +│ │ uvicorn + FastAPI │ │ Static build (Vite) │ │ +│ │ (port 8000) │ │ (served by proxy) │ │ +│ └────────┬──────────┘ └──────────────────────┘ │ +│ │ │ +│ ┌────────┴──────────────────────────────────┐ │ +│ │ fail2ban (systemd service) │ │ +│ │ Socket: /var/run/fail2ban/fail2ban.sock │ │ +│ │ Database: /var/lib/fail2ban/fail2ban.db │ │ +│ └───────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────┘ +``` + +- The backend runs as an ASGI server (uvicorn) behind a reverse proxy. +- The frontend is built to static files by Vite and served directly by the reverse proxy. +- The backend process needs read access to the fail2ban socket and the fail2ban database. +- Both the application database and the fail2ban database reside on the same host. + +--- + +## 10. Design Principles + +These principles govern all architectural decisions in BanGUI. + +| Principle | Application | +|---|---| +| **Separation of Concerns** | Frontend and backend are independent. Backend layers (router → service → repository) never mix responsibilities. | +| **Single Responsibility** | Each module, service, and component has one well-defined job. | +| **Dependency Inversion** | Services depend on abstractions (protocols), not concrete implementations. FastAPI `Depends()` wires everything. | +| **Async Everything** | All I/O is non-blocking. No synchronous database, HTTP, or socket calls anywhere in the backend. | +| **Validate at the Boundary** | Pydantic models validate all data entering the backend. TypeScript types enforce structure on the frontend. | +| **Fail Fast** | Configuration is validated at startup. Invalid input is rejected immediately with clear errors. | +| **Composition over Inheritance** | Small, focused objects are composed together rather than building deep class hierarchies. | +| **DRY** | Shared logic lives in utils, hooks, or base services — never duplicated across modules. | +| **KISS** | The simplest correct solution wins. No premature abstractions or over-engineering. | +| **YAGNI** | Only build what is needed now. Extend when a real requirement appears. | + diff --git a/Docs/Backend-Development.md b/Docs/Backend-Development.md new file mode 100644 index 0000000..8a4e444 --- /dev/null +++ b/Docs/Backend-Development.md @@ -0,0 +1,430 @@ +# Backend Development — Rules & Guidelines + +Rules and conventions every backend developer must follow. Read this before writing your first line of code. + +--- + +## 1. Language & Typing + +- **Python 3.12+** is the minimum version. +- **Every** function, method, and variable must have explicit type annotations — no exceptions. +- Use `str`, `int`, `float`, `bool`, `None` for primitives. +- Use `list[T]`, `dict[K, V]`, `set[T]`, `tuple[T, ...]` (lowercase, built-in generics) — never `typing.List`, `typing.Dict`, etc. +- Use `T | None` instead of `Optional[T]`. +- Use `TypeAlias`, `TypeVar`, `Protocol`, and `NewType` when they improve clarity. +- Return types are **mandatory** — including `-> None`. +- Never use `Any` unless there is no other option and a comment explains why. +- Run `mypy --strict` (or `pyright` in strict mode) — the codebase must pass with zero errors. + +```python +# Good +def get_jail_by_name(name: str) -> Jail | None: + ... + +# Bad — missing types +def get_jail_by_name(name): + ... +``` + +--- + +## 2. Core Libraries + +| Purpose | Library | Notes | +|---|---|---| +| Web framework | **FastAPI** | Async endpoints only. | +| Data validation & settings | **Pydantic v2** | All request/response bodies and config models. | +| Async HTTP client | **aiohttp** (`ClientSession`) | For external calls (blocklists, IP lookups). | +| Scheduling | **APScheduler 4.x** (async) | Blocklist imports, periodic health checks. | +| Structured logging | **structlog** | Every log call must use structlog — never `print()` or `logging` directly. | +| Database | **aiosqlite** | Async SQLite access for the application database. | +| Testing | **pytest** + **pytest-asyncio** + **httpx** (`AsyncClient`) | Every feature needs tests. | +| Mocking | **unittest.mock** / **pytest-mock** | Isolate external dependencies. | +| Date & time | **datetime** (stdlib) — always timezone-aware | Use `datetime.datetime.now(datetime.UTC)`. Never naive datetimes. | +| IP / Network | **ipaddress** (stdlib) | Validate and normalise IPs and CIDR ranges. | +| Environment / config | **pydantic-settings** | Load `.env` and environment variables into typed models. | +| fail2ban integration | **fail2ban client** (bundled) | Use the local copy at [`./fail2ban-master`](../fail2ban-master). Import from [`./fail2ban-master/fail2ban/client`](../fail2ban-master/fail2ban/client) to communicate with the fail2ban socket. Do **not** install fail2ban as a pip package. | + +### fail2ban Client Usage + +The repository ships with a vendored copy of fail2ban located at `./fail2ban-master`. +All communication with the fail2ban daemon must go through the client classes found in `./fail2ban-master/fail2ban/client`. +Add the project root to `sys.path` (or configure it in `pyproject.toml` as a path dependency) so that `from fail2ban.client ...` resolves to the bundled copy. + +```python +import sys +from pathlib import Path + +# Ensure the bundled fail2ban is importable +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "fail2ban-master")) + +from fail2ban.client.csocket import CSSocket # noqa: E402 +``` + +### Libraries you must NOT use + +- `requests` — use `aiohttp` (async). +- `flask` — we use FastAPI. +- `celery` — we use APScheduler. +- `print()` for logging — use `structlog`. +- `json.loads` / `json.dumps` on Pydantic models — use `.model_dump()` / `.model_validate()`. + +--- + +## 3. Project Structure + +``` +backend/ +├── app/ +│ ├── __init__.py +│ ├── main.py # FastAPI app factory, lifespan +│ ├── config.py # Pydantic settings +│ ├── dependencies.py # FastAPI dependency providers +│ ├── models/ # Pydantic schemas (request, response, domain) +│ ├── routers/ # FastAPI routers grouped by feature +│ ├── services/ # Business logic — one service per domain +│ ├── repositories/ # Database access layer +│ ├── tasks/ # APScheduler jobs +│ └── utils/ # Helpers, constants, shared types +├── tests/ +│ ├── conftest.py +│ ├── test_routers/ +│ ├── test_services/ +│ └── test_repositories/ +├── pyproject.toml +└── .env.example +``` + +- **Routers** receive requests, validate input via Pydantic, and delegate to **services**. +- **Services** contain business logic and call **repositories** or external clients. +- **Repositories** handle raw database queries — nothing else. +- Never put business logic inside routers or repositories. + +--- + +## 4. FastAPI Conventions + +- Use **async def** for every endpoint — no sync endpoints. +- Every endpoint must declare explicit **response models** (`response_model=...`). +- Use **Pydantic models** for request bodies and query parameters — never raw dicts. +- Use **Depends()** for dependency injection (database sessions, services, auth). +- Group endpoints into routers by feature domain (`routers/jails.py`, `routers/bans.py`, …). +- Use appropriate HTTP status codes: `201` for creation, `204` for deletion with no body, `404` for not found, etc. +- Use **HTTPException** or custom exception handlers — never return error dicts manually. + +```python +from fastapi import APIRouter, Depends, HTTPException, status +from app.models.jail import JailResponse, JailListResponse +from app.services.jail_service import JailService + +router: APIRouter = APIRouter(prefix="/api/jails", tags=["Jails"]) + +@router.get("/", response_model=JailListResponse) +async def list_jails(service: JailService = Depends()) -> JailListResponse: + jails: list[JailResponse] = await service.get_all_jails() + return JailListResponse(jails=jails) +``` + +--- + +## 5. Pydantic Models + +- Every model inherits from `pydantic.BaseModel`. +- Use `model_config = ConfigDict(strict=True)` where appropriate. +- Field names use **snake_case** in Python, export as **camelCase** to the frontend via alias generators if needed. +- Validate at the boundary — once data enters a Pydantic model it is trusted. +- Use `Field(...)` with descriptions for every field to keep auto-generated docs useful. +- Separate **request models**, **response models**, and **domain (internal) models** — do not reuse one model for all three. + +```python +from pydantic import BaseModel, Field +from datetime import datetime + +class BanResponse(BaseModel): + ip: str = Field(..., description="Banned IP address") + jail: str = Field(..., description="Jail that issued the ban") + banned_at: datetime = Field(..., description="UTC timestamp of the ban") + expires_at: datetime | None = Field(None, description="UTC expiry, None if permanent") + ban_count: int = Field(..., ge=1, description="Number of times this IP was banned") +``` + +--- + +## 6. Async Rules + +- **Never** call blocking / synchronous I/O in an async function — no `time.sleep()`, no synchronous file reads, no `requests.get()`. +- Use `aiohttp.ClientSession` for HTTP calls, `aiosqlite` for database access. +- Use `asyncio.TaskGroup` (Python 3.11+) when you need to run independent coroutines concurrently. +- Long-running startup/shutdown logic goes into the **FastAPI lifespan** context manager. +- Shared resources (DB connections, HTTP sessions) are created once during startup and closed during shutdown — never inside request handlers. + +```python +from contextlib import asynccontextmanager +from collections.abc import AsyncGenerator +from fastapi import FastAPI +import aiohttp +import aiosqlite + +@asynccontextmanager +async def lifespan(app: FastAPI) -> AsyncGenerator[None]: + # Startup + app.state.http_session = aiohttp.ClientSession() + app.state.db = await aiosqlite.connect("bangui.db") + yield + # Shutdown + await app.state.http_session.close() + await app.state.db.close() +``` + +--- + +## 7. Logging + +- Use **structlog** for every log message. +- Bind contextual key-value pairs — never format strings manually. +- Log levels: `debug` for development detail, `info` for operational events, `warning` for recoverable issues, `error` for failures, `critical` for fatal problems. +- Never log sensitive data (passwords, tokens, session IDs). + +```python +import structlog + +log: structlog.stdlib.BoundLogger = structlog.get_logger() + +async def ban_ip(ip: str, jail: str) -> None: + log.info("banning_ip", ip=ip, jail=jail) + try: + await _execute_ban(ip, jail) + log.info("ip_banned", ip=ip, jail=jail) + except BanError as exc: + log.error("ban_failed", ip=ip, jail=jail, error=str(exc)) + raise +``` + +--- + +## 8. Error Handling + +- Define **custom exception classes** for domain errors (e.g., `JailNotFoundError`, `BanFailedError`). +- Catch specific exceptions — never bare `except:` or `except Exception:` without re-raising. +- Map domain exceptions to HTTP status codes via FastAPI **exception handlers** registered on the app. +- Always log errors with context before raising. + +```python +class JailNotFoundError(Exception): + def __init__(self, name: str) -> None: + self.name: str = name + super().__init__(f"Jail '{name}' not found") + +# In main.py +@app.exception_handler(JailNotFoundError) +async def jail_not_found_handler(request: Request, exc: JailNotFoundError) -> JSONResponse: + return JSONResponse(status_code=404, content={"detail": f"Jail '{exc.name}' not found"}) +``` + +--- + +## 9. Testing + +- **Every** new feature or bug fix must include tests. +- Tests live in `tests/` mirroring the `app/` structure. +- Use `pytest` with `pytest-asyncio` for async tests. +- Use `httpx.AsyncClient` to test FastAPI endpoints (not `TestClient` which is sync). +- Mock external dependencies (fail2ban socket, aiohttp calls) — tests must never touch real infrastructure. +- Aim for **>80 % line coverage** — critical paths (auth, banning, scheduling) must be 100 %. +- Test names follow `test___` pattern. + +```python +import pytest +from httpx import AsyncClient, ASGITransport +from app.main import create_app + +@pytest.fixture +async def client() -> AsyncClient: + app = create_app() + transport: ASGITransport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as ac: + yield ac + +@pytest.mark.asyncio +async def test_list_jails_returns_200(client: AsyncClient) -> None: + response = await client.get("/api/jails/") + assert response.status_code == 200 + data: dict = response.json() + assert "jails" in data +``` + +--- + +## 10. Code Style & Tooling + +| Tool | Purpose | +|---|---| +| **Ruff** | Linter and formatter (replaces black, isort, flake8). | +| **mypy** or **pyright** | Static type checking in strict mode. | +| **pre-commit** | Run ruff + type checker before every commit. | + +- Line length: **120 characters** max. +- Strings: use **double quotes** (`"`). +- Imports: sorted by ruff — stdlib → third-party → local, one import per line. +- No unused imports, no unused variables, no `# type: ignore` without explanation. +- Docstrings in **Google style** on every public function, class, and module. + +--- + +## 11. Configuration & Secrets + +- All configuration lives in **environment variables** loaded through **pydantic-settings**. +- Secrets (master password hash, session key) are **never** committed to the repository. +- Provide a `.env.example` with all keys and placeholder values. +- Validate config at startup — fail fast with a clear error if a required value is missing. + +```python +from pydantic_settings import BaseSettings +from pydantic import Field + +class Settings(BaseSettings): + database_path: str = Field("bangui.db", description="Path to SQLite database") + fail2ban_socket: str = Field("/var/run/fail2ban/fail2ban.sock", description="fail2ban socket path") + session_secret: str = Field(..., description="Secret key for session signing") + log_level: str = Field("info", description="Logging level") + + model_config = {"env_prefix": "BANGUI_", "env_file": ".env"} +``` + +--- + +## 12. Git & Workflow + +- **Branch naming:** `feature/`, `fix/`, `chore/`. +- **Commit messages:** imperative tense, max 72 chars first line (`Add jail reload endpoint`, `Fix ban history query`). +- Every merge request must pass: ruff, type checker, all tests. +- Do not merge with failing CI. +- Keep pull requests small and focused — one feature or fix per PR. + +--- + +## 13. Coding Principles + +These principles are **non-negotiable**. Every backend contributor must internalise and apply them daily. + +### 13.1 Clean Code + +- Write code that **reads like well-written prose** — a new developer should understand intent without asking. +- **Meaningful names** — variables, functions, and classes must reveal their purpose. Avoid abbreviations (`cnt`, `mgr`, `tmp`) unless universally understood. +- **Small functions** — each function does exactly one thing. If you need a comment to explain a block inside a function, extract it into its own function. +- **No magic numbers or strings** — use named constants. +- **Boy Scout Rule** — leave every file cleaner than you found it. +- **Avoid deep nesting** — prefer early returns (guard clauses) to keep the happy path at the top indentation level. + +```python +# Good — guard clause, clear name, one job +async def get_active_ban(ip: str, jail: str) -> Ban: + ban: Ban | None = await repo.find_ban(ip=ip, jail=jail) + if ban is None: + raise BanNotFoundError(ip=ip, jail=jail) + if ban.is_expired(): + raise BanExpiredError(ip=ip, jail=jail) + return ban + +# Bad — nested, vague name +async def check(ip, j): + b = await repo.find_ban(ip=ip, jail=j) + if b: + if not b.is_expired(): + return b + else: + raise Exception("expired") + else: + raise Exception("not found") +``` + +### 13.2 Separation of Concerns (SoC) + +- Each module, class, and function must have a **single, well-defined responsibility**. +- **Routers** → HTTP layer only (parse requests, return responses). +- **Services** → business logic and orchestration. +- **Repositories** → data access and persistence. +- **Models** → data shapes and validation. +- **Tasks** → scheduled background jobs. +- Never mix layers — a router must not execute SQL, and a repository must not raise `HTTPException`. + +### 13.3 Single Responsibility Principle (SRP) + +- A class or module should have **one and only one reason to change**. +- If a service handles both ban management *and* email notifications, split it into `BanService` and `NotificationService`. + +### 13.4 Don't Repeat Yourself (DRY) + +- Extract shared logic into utility functions, base classes, or dependency providers. +- If the same block of code appears in more than one place, **refactor it** into a single source of truth. +- But don't over-abstract — premature DRY that couples unrelated features is worse than a little duplication (see **Rule of Three**: refactor when something appears a third time). + +### 13.5 KISS — Keep It Simple, Stupid + +- Choose the simplest solution that works correctly. +- Avoid clever tricks, premature optimisation, and over-engineering. +- If a standard library function does the job, prefer it over a custom implementation. + +### 13.6 YAGNI — You Aren't Gonna Need It + +- Do **not** build features, abstractions, or config options "just in case". +- Implement what is required **now**. Extend later when a real need emerges. + +### 13.7 Dependency Inversion Principle (DIP) + +- High-level modules (services) must not depend on low-level modules (repositories) directly. Both should depend on **abstractions** (protocols / interfaces). +- Use FastAPI's `Depends()` to inject implementations — this makes swapping and testing trivial. + +```python +from typing import Protocol + +class BanRepository(Protocol): + async def find_ban(self, ip: str, jail: str) -> Ban | None: ... + async def save_ban(self, ban: Ban) -> None: ... + +class SqliteBanRepository: + """Concrete implementation — depends on aiosqlite.""" + async def find_ban(self, ip: str, jail: str) -> Ban | None: ... + async def save_ban(self, ban: Ban) -> None: ... +``` + +### 13.8 Composition over Inheritance + +- Favour **composing** small, focused objects over deep inheritance hierarchies. +- Use mixins or protocols only when a clear "is-a" relationship exists; otherwise, pass collaborators as constructor arguments. + +### 13.9 Fail Fast + +- Validate inputs as early as possible — at the API boundary with Pydantic, at service entry with assertions or domain checks. +- Raise specific exceptions immediately rather than letting bad data propagate silently. + +### 13.10 Law of Demeter (Principle of Least Knowledge) + +- A function should only call methods on: + 1. Its own object (`self`). + 2. Objects passed as parameters. + 3. Objects it creates. +- Avoid long accessor chains like `request.state.db.cursor().execute(...)` — wrap them in a meaningful method. + +### 13.11 Defensive Programming + +- Never trust external input — validate and sanitise everything that crosses a boundary (HTTP request, file, socket, environment variable). +- Handle edge cases explicitly: empty lists, `None` values, negative numbers, empty strings. +- Use type narrowing and exhaustive pattern matching (`match` / `case`) to eliminate impossible states. + +--- + +## 14. Quick Reference — Do / Don't + +| Do | Don't | +|---|---| +| Type every function, variable, return | Leave types implicit | +| Use `async def` for I/O | Use sync functions for I/O | +| Validate with Pydantic at the boundary | Pass raw dicts through the codebase | +| Log with structlog + context keys | Use `print()` or format strings in logs | +| Write tests for every feature | Ship untested code | +| Use `aiohttp` for HTTP calls | Use `requests` | +| Handle errors with custom exceptions | Use bare `except:` | +| Keep routers thin, logic in services | Put business logic in routers | +| Use `datetime.now(datetime.UTC)` | Use naive datetimes | +| Run ruff + mypy before committing | Push code that doesn't pass linting | \ No newline at end of file diff --git a/Docs/Features.md b/Docs/Features.md new file mode 100644 index 0000000..aba3372 --- /dev/null +++ b/Docs/Features.md @@ -0,0 +1,272 @@ +# BanGUI — Feature List + +A web application to monitor, manage, and configure fail2ban from a clean, accessible interface. + +--- + +## 1. Setup Page + +- Displayed automatically on first launch when no configuration exists. +- As long as no configuration is saved, every route redirects to the setup page. +- Once setup is complete and a configuration is saved, the setup page is never shown again and cannot be accessed. + +### Options + +- **Master Password** — Set a single global password that protects the entire web interface. +- **Database Path** — Define where the application stores its own SQLite database. +- **fail2ban Connection** — Specify how the application connects to the running fail2ban instance (socket path or related settings). +- **General Preferences** — Any additional application-level settings such as default time zone, date format, or session duration. + +--- + +## 2. Login Page + +- A simple password prompt with a single input field and a submit button. +- No username — only the master password set during setup. +- Every page in the application (except the setup page) requires the user to be authenticated. +- If a user is not logged in and tries to access any page, they are automatically redirected to the login page. +- After entering the correct password the user is taken to the page they originally requested. +- A logout option is available from every page so the user can end their session. + +--- + +## 3. Ban Overview (Dashboard) + +The main landing page after login. Shows recent ban activity at a glance. + +### Server Status Bar + +- A persistent status indicator showing whether the fail2ban server is running or unreachable. +- Displays the fail2ban version, the number of active jails, and total bans across all jails. +- Shows combined statistics: total failures detected and total bans issued since the server started. + +### Ban List + +- A table displaying all IPs that were banned within a selected time window. +- **Columns:** Time of ban, IP address, requested URL / service, country of origin, target domain, target subdomain. +- Rows are sorted by time, newest first. +- A time-range selector with quick presets: + - Last 24 hours + - Last 7 days (week) + - Last 30 days (month) + - Last 365 days (year) + +### Access List + +- A secondary view (tab or toggle) on the same page showing **all recorded accesses**, not just bans. +- Uses the same table format: time, IP address, requested URL, country, domain, subdomain. +- Shares the same time-range presets so the user can compare total traffic against banned traffic for the same period. + +--- + +## 4. World Map View + +A geographical overview of ban activity. + +### Map + +- A full world map rendered with country outlines only (no fill colours, no satellite imagery). +- For every country that has at least one banned IP in the selected time range, the total count is displayed centred inside that country's borders. +- Countries with zero banned IPs show no number and no label — they remain blank. +- Time-range selector with the same quick presets: + - Last 24 hours + - Last 7 days + - Last 30 days + - Last 365 days + +### Access List (Map context) + +- A companion table below or beside the map listing all accesses for the selected time range. +- Same columns as the Ban Overview tables: time, IP, URL, country, domain, subdomain. +- Selecting a country on the map filters the table to show only entries from that country. + +--- + +## 5. Jail Management + +A dedicated view for managing fail2ban jails and taking manual ban actions. + +### Jail Overview + +- A list of all jails showing their name, current status (running / stopped / idle), backend type, and key metrics. +- For each jail: number of currently banned IPs, total bans since start, current failures detected, and total failures. +- Quick indicators for the jail's find time, ban time, and max retries. + +### Jail Detail + +- Selecting a jail opens a detailed view with all of its settings. +- Shows every monitored log file path attached to that jail. +- Shows all fail regex and ignore regex patterns in a readable list. +- Shows the date pattern and log encoding used for parsing. +- Shows all actions attached to the jail and their configuration. +- Shows ban-time escalation settings if incremental banning is enabled (factor, formula, multipliers, max time). + +### Jail Controls + +- Start or stop individual jails. +- Toggle a jail into idle mode (pauses monitoring without fully stopping it) and back. +- Reload a single jail to pick up configuration changes without restarting the entire server. +- Reload all jails at once. + +### Ban an IP + +- Input field to enter an IP address. +- Option to select which jail the ban should apply to. +- Confirm and execute the ban. +- Visual feedback confirming the IP has been banned successfully. + +### Unban an IP + +- Input field to enter an IP address, or select from the list of currently banned IPs. +- Option to select the jail from which the IP should be unbanned, or unban from all jails. +- Option to unban all IPs at once across every jail. +- Confirm and execute the unban. +- Visual feedback confirming the IP has been unbanned successfully. + +### Currently Banned IPs + +- A quick-reference list of all IPs that are currently banned across all jails. +- Each entry shows the IP, the jail it belongs to, when the ban started, and when it will expire (if applicable). +- Shows how many times this IP has been banned before (ban count / repeat offender indicator). +- Direct unban button next to each entry for convenience. + +### IP Lookup + +- Enter any IP address to check whether it is currently banned, and if so in which jails. +- Show the ban history for that IP: how many times it was banned, when each ban occurred, and from which jails. +- Display enriched IP information: country, ASN (network operator), and Regional Internet Registry (RIR). + +### IP Whitelist (Ignore List) + +- View the list of IP addresses or networks that are excluded from banning, per jail and globally. +- Add an IP address or network range to a jail's ignore list so it will never be banned. +- Remove an IP from the ignore list. +- Toggle the "ignore self" option per jail, which automatically excludes the server's own IP addresses. + +--- + +## 6. Configuration View + +A page to inspect and modify the fail2ban configuration without leaving the web interface. + +### View Configuration + +- Display all active fail2ban jails and their current settings. +- For each jail, show the associated filter and its regex patterns in a readable format. +- Show global fail2ban settings (ban time, find time, max retries, etc.). + +### Edit Configuration + +- Inline editing of jail parameters (ban time, max retries, enabled/disabled, etc.). +- Inline editing of filter regex patterns. +- Add or remove fail regex and ignore regex patterns per jail. +- Edit the prefix regex used for pre-filtering log lines. +- Configure the date pattern and log time zone for each jail. +- Configure DNS resolution mode per jail (resolve all hostnames, IP only, etc.). +- Configure ban-time escalation: enable incremental banning and set factor, formula, multipliers, maximum ban time, and random jitter. +- Save changes and optionally reload fail2ban to apply them immediately. +- Validation feedback if a regex pattern or setting value is invalid before saving. + +### Add Log Observation + +- Option to register additional log files that fail2ban should monitor. +- For each new log, specify: + - The path to the log file. + - One or more regex patterns that define what constitutes a failure. + - The jail name and basic jail settings (ban time, retries, etc.). +- Choose whether the file should be read from the beginning or only new lines (head vs. tail). +- Preview matching lines from the log against the provided regex before saving, so the user can verify the pattern works. + +### Regex Tester + +- Paste or type a sample log line into a text field. +- Enter a fail regex pattern. +- Immediately see whether the pattern matches the sample, with the matched groups highlighted. +- Useful for crafting and debugging new filter rules before applying them to a live jail. + +### Server Settings + +- View and change the fail2ban log level (e.g. Critical, Error, Warning, Info, Debug). +- View and change the log target (file path, stdout, stderr, syslog, systemd journal). +- View and change the syslog socket if syslog is used. +- Flush and re-open log files (useful after log rotation). +- View and change the fail2ban database file location. +- Set the database purge age — how long historical ban records are kept before automatic cleanup. +- Set the maximum number of log-line matches stored per ban record in the database. + +--- + +## 7. Ban History + +A view for exploring historical ban data stored in the fail2ban database. + +### History Table + +- Browse all past bans across all jails, not just the currently active ones. +- **Columns:** Time of ban, IP address, jail, ban duration, ban count (how many times this IP was banned), country. +- Filter by jail, by IP address, or by time range. +- See at a glance which IPs are repeat offenders (high ban count). + +### Per-IP History + +- Select any IP to see its full ban timeline: every ban event, which jail triggered it, when it started, and how long it lasted. +- Merged view showing total failures and matched log lines aggregated across all bans for that IP. + +--- + +## 8. External Blocklist Importer + +Automated downloading and applying of external IP blocklists to block known malicious IPs on a recurring schedule. + +### Blocklist Sources + +- Manage a list of external blocklist URLs (e.g. `https://lists.blocklist.de/lists/all.txt`). +- Add, edit, or remove blocklist source URLs through the web interface. +- Each source has a name, URL, and an enabled/disabled toggle. +- Support for plain-text lists with one IP address per line. +- Preview the contents of a blocklist URL before enabling it (download and display a sample of entries). + +### Schedule + +- Configure when the blocklist import runs using a simple time-and-frequency picker (no raw cron syntax required). +- Default schedule: daily at 03:00 (server local time). +- Available frequency presets: + - Every X hours + - Daily at a specific time + - Weekly on a specific day and time +- Option to run an import manually at any time via a "Run Now" button. +- Show the date and time of the last successful import and the next scheduled run. + +### Import Behaviour + +- On each scheduled run, download all enabled blocklist sources. +- Validate that each downloaded entry is a well-formed IP address or CIDR range before applying it. +- Apply downloaded IPs as bans through fail2ban (preferred) or directly via an iptables chain, depending on configuration. +- If using an iptables chain: flush the chain before re-populating it so stale entries are removed automatically. +- If using fail2ban: ban each IP in a dedicated jail created for blocklist imports so they are tracked separately from regular bans. +- Skip empty lines and malformed entries gracefully instead of aborting the entire import. +- Clean up temporary downloaded files after processing. + +### Import Log + +- Keep a log of every import run: timestamp, source URL, number of IPs imported, number of entries skipped (invalid), and any errors. +- Display the import log in the web interface, filterable by source and date range. +- Show a warning badge in the navigation if the most recent import encountered errors. + +### Error Handling + +- If a blocklist URL is unreachable, log the error and continue with remaining sources. +- If curl (or the download mechanism) is unavailable, surface a clear error in the import log. +- Notify the user (via the UI status bar) when a scheduled import fails so it does not go unnoticed. + +--- + +## 9. General Behaviour + +- **Responsive layout** — Usable on desktop and tablet screens. +- **Session persistence** — The user stays logged in until they explicitly log out or the session expires. +- **Consistent navigation** — A sidebar or top navigation bar is visible on every page, providing quick access to all sections: Dashboard, World Map, Jails, Configuration, History, and Logout. +- **Time zone awareness** — All displayed times respect the time zone configured during setup. +- **Connection health** — The application continuously checks whether the fail2ban server is reachable and shows a clear warning when the connection is lost. + + diff --git a/Docs/Instructions.md b/Docs/Instructions.md new file mode 100644 index 0000000..cc0c3be --- /dev/null +++ b/Docs/Instructions.md @@ -0,0 +1,228 @@ +# AI Agent — General Instructions + +You are an autonomous coding agent working on **BanGUI**, a web application for monitoring, managing, and configuring fail2ban through a clean web interface. This document defines how you operate, what rules you follow, and which workflow you repeat for every task. + +Read this document completely before starting any work. + +--- + +## 1. Project Context + +BanGUI consists of two main parts: + +| Layer | Stack | Docs | +|---|---|---| +| **Backend** | Python 3.12+, FastAPI, Pydantic v2, aiosqlite, structlog | [Backend-Development.md](Backend-Development.md) | +| **Frontend** | TypeScript, React, Fluent UI v9, Vite | [Web-Development.md](Web-Development.md) | + +Supporting documentation you must know and respect: + +| Document | Purpose | +|---|---| +| [Features.md](Features.md) | Complete feature list and expected behaviour | +| [Architekture.md](Architekture.md) | System architecture, component relationships, data flow | +| [Web-Design.md](Web-Design.md) | Visual design rules, theming, layout, spacing, motion | +| [Backend-Development.md](Backend-Development.md) | Backend coding rules, project structure, conventions | +| [Web-Development.md](Web-Development.md) | Frontend coding rules, project structure, conventions | + +**Always** consult the relevant document before writing code. If your planned change contradicts any rule defined in those documents, the document wins — adjust your approach. + +--- + +## 2. General Rules + +### 2.1 Follow the Docs + +- Every coding convention, naming rule, project structure decision, and library choice is defined in the development docs. Do not deviate. +- Backend code follows [Backend-Development.md](Backend-Development.md) — strict typing, async only, structlog, Pydantic models, layered architecture (routers → services → repositories). +- Frontend code follows [Web-Development.md](Web-Development.md) — strict TypeScript, Fluent UI v9 only, `makeStyles` for styling, typed API calls, hooks for state. +- Visual decisions follow [Web-Design.md](Web-Design.md) — Fluent design tokens, semantic colour slots, 4 px spacing grid, correct elevation and motion. + +### 2.2 Write Production-Quality Code + +- Write code as if it ships today. No TODOs, no placeholders, no half-implementations. +- Every function has explicit type annotations (Python) or type signatures (TypeScript). +- Every public function has a docstring (Python — Google style) or JSDoc comment (TypeScript). +- No `any` in TypeScript. No `Any` in Python (unless justified with a comment). +- No magic numbers or strings — use named constants. +- No dead code, no commented-out blocks, no unused imports. + +### 2.3 Keep It Small and Focused + +- One function does one thing. +- One component per file. +- One service per domain. +- If a file grows beyond ~150 lines (components) or ~200 lines (services), split it. + +### 2.4 Never Break Existing Code + +- Before changing any file, understand what it does and who depends on it. +- Run the existing test suite before and after your changes. If tests fail after your change, fix them before moving on. +- Do not remove or rename public APIs without updating all callers. + +### 2.5 Think Before You Code + +- Read the task description carefully. If it is ambiguous, check [Features.md](Features.md) and [Architekture.md](Architekture.md) for clarification. +- Plan your changes before writing code. Identify which files are affected, which layers are involved, and what tests are needed. +- Prefer the simplest correct solution. Do not over-engineer. + +--- + +## 3. Task Workflow + +Repeat the following cycle for every task. Do not skip steps. + +### Step 1 — Pick a Task + +- Open `tasks.md` and pick the next unfinished task (highest priority first). +- Mark the task as **in progress**. +- Read the task description thoroughly. Understand the expected outcome before proceeding. + +### Step 2 — Plan Your Steps + +- Break the task into concrete implementation steps. +- Identify which files need to be created, modified, or deleted. +- Identify which layers are affected (router, service, repository, model, component, hook, page, type, etc.). +- Identify edge cases and error scenarios. +- Write down your plan before touching any code. + +### Step 3 — Write Code + +- Implement the feature or fix following the plan. +- Follow all rules from the relevant development docs: + - Backend → [Backend-Development.md](Backend-Development.md) + - Frontend → [Web-Development.md](Web-Development.md) + - Design → [Web-Design.md](Web-Design.md) + - Architecture → [Architekture.md](Architekture.md) +- Write clean, well-structured, fully typed code. +- Keep commits atomic — one logical change per commit. + +### Step 4 — Add Logging + +- Add structured log statements at key points in new or modified code. +- Backend: use **structlog** with contextual key-value pairs — never `print()`. +- Log at appropriate levels: `info` for operational events, `warning` for recoverable issues, `error` for failures. +- Never log sensitive data (passwords, tokens, session IDs). + +### Step 5 — Write Tests + +- Write tests for every new or changed piece of functionality. +- Backend: use `pytest` + `pytest-asyncio` + `httpx.AsyncClient`. See [Backend-Development.md § 9](Backend-Development.md). +- Frontend: test components and hooks according to the frontend test setup. +- Test the happy path **and** error/edge cases. +- Mock external dependencies — tests must never touch real infrastructure. +- Follow the naming pattern: `test___`. + +### Step 6 — Review Your Code + +Run a thorough self-review before considering the task done. Check **all** of the following: + +#### 6.1 — Warnings and Errors + +- Backend: run `ruff check` and `mypy --strict` (or `pyright --strict`). Fix every warning and error. +- Frontend: run `tsc --noEmit` and `eslint`. Fix every warning and error. +- Zero warnings, zero errors — no exceptions. + +#### 6.2 — Test Coverage + +- Run the test suite with coverage enabled. +- Aim for **>80 % line coverage** overall. +- Critical paths (auth, banning, scheduling, API endpoints) must be **100 %** covered. +- If coverage is below the threshold, write additional tests before proceeding. + +#### 6.3 — Coding Principles + +Verify your code against the coding principles defined in [Backend-Development.md § 13](Backend-Development.md) and [Web-Development.md](Web-Development.md): + +- [ ] **Clean Code** — Meaningful names, small functions, no magic values, guard clauses over deep nesting. +- [ ] **Separation of Concerns** — Each module has a single, well-defined responsibility. Layers are not mixed. +- [ ] **Single Responsibility Principle** — Every class and function has one reason to change. +- [ ] **DRY** — No duplicated logic. Shared behaviour is extracted. +- [ ] **KISS** — The simplest correct solution is used. No over-engineering. +- [ ] **Type Safety** — All types are explicit. No `any` / `Any`. No `# type: ignore` without justification. + +#### 6.4 — Architecture Compliance + +Verify against [Architekture.md](Architekture.md) and the project structure rules: + +- [ ] Files are in the correct directories (routers in `routers/`, services in `services/`, components in `components/`, etc.). +- [ ] Dependencies flow in the right direction (routers → services → repositories; pages → components → hooks). +- [ ] No circular imports. +- [ ] No business logic in routers or components. +- [ ] No HTTP/framework concerns in services or repositories. +- [ ] Pydantic models separate request, response, and domain shapes. +- [ ] Frontend types live in `types/`, not scattered across components. + +### Step 7 — Update Documentation + +- If your change introduces new features, new endpoints, new components, or changes existing behaviour, update the relevant docs: + - [Features.md](Features.md) — if feature behaviour changed. + - [Architekture.md](Architekture.md) — if new modules, services, or data flows were added. + - [Backend-Development.md](Backend-Development.md) or [Web-Development.md](Web-Development.md) — if new conventions were established. +- Keep documentation accurate and in sync with the code. Outdated docs are worse than no docs. + +### Step 8 — Mark Task Complete + +- Open `tasks.md` and mark the task as **done**. +- Add a brief summary of what was implemented or changed. + +### Step 9 — Commit + +- Stage all changed files. +- Write a commit message in **imperative tense**, max 72 characters for the subject line. + - Good: `Add jail reload endpoint` + - Bad: `added stuff` / `WIP` / `fix` +- If the change is large, include a body explaining **why**, not just **what**. +- Branch naming: `feature/`, `fix/`, `chore/`. +- Ensure the commit passes: linter, type checker, all tests. + +### Step 10 — Next Task + +- Return to **Step 1** and pick the next task. + +--- + +## 4. Workflow Summary + +``` +┌─────────────────────────────────────────┐ +│ 1. Pick task from tasks.md │ +│ 2. Plan your steps │ +│ 3. Write code │ +│ 4. Add logging │ +│ 5. Write tests │ +│ 6. Review your code │ +│ ├── 6.1 Check warnings & errors │ +│ ├── 6.2 Check test coverage │ +│ ├── 6.3 Check coding principles │ +│ └── 6.4 Check architecture │ +│ 7. Update documentation if needed │ +│ 8. Mark task complete in tasks.md │ +│ 9. Git commit │ +│ 10. Pick next task ──────── loop ───┐ │ +│ ▲ │ │ +│ └───────────────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +--- + +## 5. When You Are Stuck + +- Re-read the task description and the relevant docs. +- Search the existing codebase for similar patterns — follow established conventions. +- Check the fail2ban source code in `fail2ban-master/` if you need to understand how fail2ban works internally. +- If a decision is genuinely ambiguous and no document covers it, choose the simplest option that is consistent with existing code and document your reasoning in a code comment. + +--- + +## 6. What You Must Never Do + +- **Never** commit code that does not compile or has type errors. +- **Never** commit code without tests. +- **Never** use libraries that are explicitly forbidden in the development docs. +- **Never** bypass the linter or type checker with blanket ignores. +- **Never** hard-code secrets, passwords, or tokens. +- **Never** push directly to `main` — always use feature branches. +- **Never** skip the review step — sloppy code compounds over time. +- **Never** leave a task half-done — finish it or revert it. diff --git a/Docs/Tasks.md b/Docs/Tasks.md new file mode 100644 index 0000000..dc3cf18 --- /dev/null +++ b/Docs/Tasks.md @@ -0,0 +1,409 @@ +# 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. + +--- + +## Stage 1 — Project Scaffolding + +Everything in this stage is about creating the project skeleton — folder structures, configuration files, and tooling — so that development can begin on solid ground. No application logic is written here. + +### 1.1 Initialise the backend project + +Create the `backend/` directory with the full folder structure defined in [Backend-Development.md § 3](Backend-Development.md). Set up `pyproject.toml` with all required dependencies (FastAPI, Pydantic v2, aiosqlite, aiohttp, APScheduler 4.x, structlog, pydantic-settings) and dev dependencies (pytest, pytest-asyncio, httpx, ruff, mypy). Configure ruff for 120-character line length and double-quote strings. Configure mypy in strict mode. Add a `.env.example` with placeholder keys for `BANGUI_DATABASE_PATH`, `BANGUI_FAIL2BAN_SOCKET`, and `BANGUI_SESSION_SECRET`. Make sure the bundled fail2ban client at `./fail2ban-master` is importable by configuring the path in `pyproject.toml` or a startup shim as described in [Backend-Development.md § 2](Backend-Development.md). + +### 1.2 Initialise the frontend project + +Scaffold a Vite + React + TypeScript project inside `frontend/`. Install `@fluentui/react-components`, `@fluentui/react-icons`, and `react-router-dom`. Set up `tsconfig.json` with `"strict": true`. Configure ESLint with `@typescript-eslint`, `eslint-plugin-react-hooks`, and `eslint-config-prettier`. Add Prettier with the project defaults. Create the directory structure from [Web-Development.md § 4](Web-Development.md): `src/api/`, `src/components/`, `src/hooks/`, `src/layouts/`, `src/pages/`, `src/providers/`, `src/theme/`, `src/types/`, `src/utils/`. Create a minimal `App.tsx` that wraps the application in `` and `` as shown in [Web-Development.md § 5](Web-Development.md). + +### 1.3 Set up the Fluent UI custom theme + +Create the light and dark brand-colour themes inside `frontend/src/theme/`. Follow the colour rules in [Web-Design.md § 2](Web-Design.md): use the Fluent UI Theme Designer to generate a brand ramp, ensure the primary colour meets the 4.5 : 1 contrast ratio, and export both `lightTheme` and `darkTheme`. Wire the theme into `App.tsx` via the `FluentProvider` `theme` prop. + +### 1.4 Create the central API client + +Build the typed API client in `frontend/src/api/client.ts`. It should be a thin wrapper around `fetch` that returns typed responses, includes credentials, and throws a custom `ApiError` on non-OK responses. Define the `BASE_URL` from `import.meta.env.VITE_API_URL` with a fallback to `"/api"`. Create `frontend/src/api/endpoints.ts` for path constants. See [Web-Development.md § 3](Web-Development.md) for the pattern. + +### 1.5 Create the FastAPI application factory + +Implement `backend/app/main.py` with the `create_app()` factory function. Register the async lifespan context manager that opens the aiosqlite database connection, creates a shared `aiohttp.ClientSession`, and initialises the APScheduler instance on startup, then closes all three on shutdown. Store these on `app.state`. Register a placeholder router so the app can start and respond to a health-check request. See [Backend-Development.md § 6](Backend-Development.md) and [Architekture.md § 2](Architekture.md) for details. + +### 1.6 Create the Pydantic settings model + +Implement `backend/app/config.py` using pydantic-settings. Define the `Settings` class with fields for `database_path`, `fail2ban_socket`, `session_secret`, `session_duration_minutes`, and `timezone`. Load from environment variables prefixed `BANGUI_` and from `.env`. Validate at startup — the app must fail fast with a clear error if required values are missing. See [Backend-Development.md § 11](Backend-Development.md). + +### 1.7 Set up the application database schema + +Design and create the SQLite schema for BanGUI's own data. The database needs tables for application settings (key-value pairs for master password hash, database path, fail2ban socket path, preferences), sessions (token, created-at, expires-at), blocklist sources (name, URL, enabled flag), and import log entries (timestamp, source URL, IPs imported, IPs skipped, errors). Write an initialisation function that creates these tables on first run via aiosqlite. This schema is for BanGUI's internal state — it does not replace the fail2ban database. See [Architekture.md § 2.2](Architekture.md) for the repository breakdown. + +### 1.8 Write the fail2ban socket client wrapper + +Implement `backend/app/utils/fail2ban_client.py` — an async wrapper around the fail2ban Unix domain socket protocol. Study `./fail2ban-master/fail2ban/client/csocket.py` and `./fail2ban-master/fail2ban/client/fail2banclient.py` to understand the wire protocol (pickle-based command/response). The wrapper should provide async methods for sending commands and receiving responses, handle connection errors gracefully, and log every interaction with structlog. This module is the single point of contact between BanGUI and the fail2ban daemon. See [Backend-Development.md § 2 (fail2ban Client Usage)](Backend-Development.md) and [Architekture.md § 2.2 (Utils)](Architekture.md). + +--- + +## Stage 2 — Authentication & Setup Flow + +This stage implements the very first user experience: the setup wizard that runs on first launch and the login system that protects every subsequent visit. All other features depend on these being complete. + +### 2.1 Implement the setup service and repository + +Build `backend/app/services/setup_service.py` and `backend/app/repositories/settings_repo.py`. The setup service accepts the initial configuration (master password, database path, fail2ban socket path, general preferences), hashes the password with a secure algorithm (e.g. bcrypt or argon2), and persists everything through the settings repository. It must enforce the one-time-only rule: once a configuration is saved, setup cannot run again. Add a method to check whether setup has been completed (i.e. whether any configuration exists in the database). See [Features.md § 1](Features.md). + +### 2.2 Implement the setup router + +Create `backend/app/routers/setup.py` with a `POST /api/setup` endpoint that accepts a Pydantic request model containing all setup fields and delegates to the setup service. If setup has already been completed, return a `409 Conflict`. Define request and response models in `backend/app/models/setup.py`. + +### 2.3 Implement the setup-redirect middleware + +Add middleware to the FastAPI app that checks on every incoming request whether setup has been completed. If not, redirect all requests (except those to `/api/setup` itself) to `/api/setup` with a `307 Temporary Redirect` or return a `403` with a clear message. Once setup is done, the middleware becomes a no-op. See [Features.md § 1](Features.md). + +### 2.4 Implement the authentication service + +Build `backend/app/services/auth_service.py`. It must verify the master password against the stored hash, create session tokens on successful login, store sessions through `backend/app/repositories/session_repo.py`, validate tokens on every subsequent request, and enforce session expiry. Sessions should be stored in the SQLite database so they survive server restarts. See [Features.md § 2](Features.md) and [Architekture.md § 2.2](Architekture.md). + +### 2.5 Implement the auth router + +Create `backend/app/routers/auth.py` with two endpoints: `POST /api/auth/login` (accepts a password, returns a session token or sets a cookie) and `POST /api/auth/logout` (invalidates the session). Define request and response models in `backend/app/models/auth.py`. + +### 2.6 Implement the auth dependency + +Create a FastAPI dependency in `backend/app/dependencies.py` that extracts the session token from the request (cookie or header), validates it through the auth service, and either returns the authenticated session or raises a `401 Unauthorized`. Every protected router must declare this dependency. See [Backend-Development.md § 4](Backend-Development.md) for the Depends pattern. + +### 2.7 Build the setup page (frontend) + +Create `frontend/src/pages/SetupPage.tsx`. The page should present a form with fields for the master password (with confirmation), database path, fail2ban socket path, and general preferences (timezone, date format, session duration). Use Fluent UI form components (`Input`, `Button`, `Field`, `Dropdown` for timezone). On submission, call `POST /api/setup` through the API client. Show validation errors inline. After successful setup, redirect to the login page. Create the corresponding API function in `frontend/src/api/setup.ts` and types in `frontend/src/types/setup.ts`. See [Features.md § 1](Features.md) and [Web-Design.md § 8](Web-Design.md) for component choices. + +### 2.8 Build the login page (frontend) + +Create `frontend/src/pages/LoginPage.tsx`. A single password input and a submit button — no username field. On submission, call `POST /api/auth/login`. On success, store the session (cookie or context) and redirect to the originally requested page or the dashboard. Show an error message on wrong password. Create `frontend/src/api/auth.ts` and `frontend/src/types/auth.ts`. See [Features.md § 2](Features.md). + +### 2.9 Implement the auth context and route guard + +Create `frontend/src/providers/AuthProvider.tsx` that manages authentication state (logged in / not logged in) and exposes login, logout, and session-check methods via React context. Create a route guard component that wraps all protected routes: if the user is not authenticated, redirect to the login page and remember the intended destination. After login, redirect back. See [Features.md § 2](Features.md) and [Web-Development.md § 7](Web-Development.md). + +### 2.10 Write tests for setup and auth + +Write backend tests covering: setup endpoint accepts valid data, setup endpoint rejects a second call, login succeeds with correct password, login fails with wrong password, protected endpoints reject unauthenticated requests, logout invalidates the session for both router and service. Use pytest-asyncio and httpx `AsyncClient` as described in [Backend-Development.md § 9](Backend-Development.md). + +--- + +## Stage 3 — Application Shell & Navigation + +With authentication working, this stage builds the persistent layout that every page shares: the navigation sidebar, the header, and the routing skeleton. + +### 3.1 Build the main layout component + +Create `frontend/src/layouts/MainLayout.tsx`. This is the outer shell visible on every authenticated page. It contains a fixed-width sidebar navigation (240 px, collapsing to 48 px on small screens) and a main content area. Use the Fluent UI `Nav` component for the sidebar with groups for Dashboard, World Map, Jails, Configuration, History, Blocklists, and a Logout action at the bottom. The layout must be responsive following the breakpoints in [Web-Design.md § 4](Web-Design.md). The main content area is capped at 1440 px and centred on wide screens. + +### 3.2 Set up client-side routing + +Configure React Router in `frontend/src/App.tsx` (or a dedicated `AppRoutes.tsx`). Define routes for every page: `/` (dashboard), `/map`, `/jails`, `/jails/:name`, `/config`, `/history`, `/blocklists`, `/setup`, `/login`. Wrap all routes except setup and login inside the auth guard from Stage 2. Use the `MainLayout` for authenticated routes. Create placeholder page components for each route so navigation works end to end. + +### 3.3 Implement the logout flow + +Wire the Logout button in the sidebar to call `POST /api/auth/logout`, clear the client-side session state, and redirect to the login page. The logout option must be accessible from every page as specified in [Features.md § 2](Features.md). + +--- + +## Stage 4 — fail2ban Connection & Server Status + +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 + +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). + +### 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). + +### 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. + +### 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. + +### 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. + +--- + +## Stage 5 — Ban Overview (Dashboard) + +The main landing page. This stage delivers the ban list and access list tables that give users a quick picture of recent activity. + +### 5.1 Implement the ban service (list recent bans) + +Build `backend/app/services/ban_service.py` with a method that queries the fail2ban database for bans within a given time range. The fail2ban SQLite database stores ban records — read them using aiosqlite (open the fail2ban DB path from settings, read-only). Return structured ban objects including IP, jail, timestamp, and any additional metadata available. See [Features.md § 3 (Ban List)](Features.md). + +### 5.2 Implement the geo service + +Build `backend/app/services/geo_service.py`. Given an IP address, resolve its country of origin (and optionally ASN and RIR). Use an external API via aiohttp or a local GeoIP database. Cache results to avoid repeated lookups for the same IP. The geo service is used throughout the application wherever country information is displayed. See [Features.md § 5 (IP Lookup)](Features.md) and [Architekture.md § 2.2](Architekture.md). + +### 5.3 Implement the dashboard bans endpoint + +Add `GET /api/dashboard/bans` to `backend/app/routers/dashboard.py`. It accepts a time-range query parameter (hours or a preset like `24h`, `7d`, `30d`, `365d`). It calls the ban service to retrieve bans in that window, enriches each ban with country data from the geo service, and returns a paginated list. Define request/response models in `backend/app/models/ban.py`. + +### 5.4 Build the ban list table (frontend) + +Create `frontend/src/components/BanTable.tsx` using Fluent UI `DataGrid`. Columns: time of ban, IP address (monospace), requested URL/service, country, domain, subdomain. Rows are sorted newest-first. Above the table, place a time-range selector implemented as a `Toolbar` with `ToggleButton` for the four presets (24 h, 7 d, 30 d, 365 d). Create a `useBans` hook that calls `GET /api/dashboard/bans` with the selected range. See [Features.md § 3 (Ban List)](Features.md) and [Web-Design.md § 8 (Data Display)](Web-Design.md). + +### 5.5 Build the dashboard page + +Create `frontend/src/pages/DashboardPage.tsx`. Compose the server status bar at the top, then a `Pivot` (tab control) switching between "Ban List" and "Access List". The Ban List tab renders the `BanTable`. The Access List tab uses the same table component but fetches all recorded accesses, not just bans. If the access list requires a separate endpoint, add `GET /api/dashboard/accesses` to the backend with the same time-range support. See [Features.md § 3](Features.md). + +### 5.6 Write tests for ban service and dashboard endpoints + +Test ban queries for each time-range preset, test that geo enrichment works with mocked API responses, and test that the endpoint returns the correct response shape. Verify edge cases: no bans in the selected range, an IP that fails geo lookup. + +--- + +## Stage 6 — Jail Management + +This stage exposes fail2ban's jail system through the UI — listing jails, viewing details, and executing control commands. + +### 6.1 Implement the jail service + +Build `backend/app/services/jail_service.py`. Using the fail2ban socket client, implement methods to: list all jails with their status and key metrics, retrieve the full detail of a single jail (log paths, regex patterns, date pattern, encoding, actions, ban-time escalation settings), start a jail, stop a jail, toggle idle mode, reload a single jail, and reload all jails. Each method sends the appropriate command through the socket wrapper and parses the response. See [Features.md § 5 (Jail Overview, Jail Detail, Jail Controls)](Features.md). + +### 6.2 Implement the jails router + +Create `backend/app/routers/jails.py`: +- `GET /api/jails` — list all jails with status and metrics. +- `GET /api/jails/{name}` — full detail for a single jail. +- `POST /api/jails/{name}/start` — start a jail. +- `POST /api/jails/{name}/stop` — stop a jail. +- `POST /api/jails/{name}/idle` — toggle idle mode. +- `POST /api/jails/{name}/reload` — reload a single jail. +- `POST /api/jails/reload-all` — reload all jails. + +Define request/response models in `backend/app/models/jail.py`. Use appropriate HTTP status codes (404 if a jail name does not exist, 409 if a jail is already in the requested state). See [Architekture.md § 2.2 (Routers)](Architekture.md). + +### 6.3 Implement ban and unban endpoints + +Add to `backend/app/routers/bans.py`: +- `POST /api/bans` — ban an IP in a specified jail. Validate the IP with `ipaddress` before sending. +- `DELETE /api/bans` — unban an IP from a specific jail or all jails. Support an `unban_all` flag. +- `GET /api/bans/active` — list all currently banned IPs across all jails, with jail name, ban start time, expiry, and ban count. + +Delegate to the ban service. See [Features.md § 5 (Ban an IP, Unban an IP, Currently Banned IPs)](Features.md). + +### 6.4 Build the jail overview page (frontend) + +Create `frontend/src/pages/JailsPage.tsx`. Display a card or table for each jail showing name, status badge (running/stopped/idle), backend type, banned count, total bans, failure counts, find time, ban time, and max retries. Each jail links to a detail view. Use Fluent UI `Card` or `DataGrid`. Create `frontend/src/api/jails.ts`, `frontend/src/types/jail.ts`, and a `useJails` hook. See [Features.md § 5 (Jail Overview)](Features.md). + +### 6.5 Build the jail detail page (frontend) + +Create `frontend/src/pages/JailDetailPage.tsx` — reached via `/jails/:name`. Fetch the full jail detail and display: monitored log paths, fail regex and ignore regex lists (rendered in monospace), date pattern, log encoding, attached actions and their config, and ban-time escalation settings. Include control buttons (Start, Stop, Idle, Reload) that call the corresponding API endpoints with confirmation dialogs (Fluent UI `Dialog`). See [Features.md § 5 (Jail Detail, Jail Controls)](Features.md). + +### 6.6 Build the ban/unban UI (frontend) + +On the Jails page (or a dedicated sub-section), add a "Ban an IP" form with an IP input field and a jail selector dropdown. Add an "Unban an IP" form with an IP input (or selection from the currently-banned list), a jail selector (or "all jails"), and an "unban all" option. Show success/error feedback using Fluent UI `MessageBar` or `Toast`. Build a "Currently Banned IPs" table showing IP, jail, ban start, expiry, ban count, and a direct unban button per row. See [Features.md § 5 (Ban an IP, Unban an IP, Currently Banned IPs)](Features.md). + +### 6.7 Implement IP lookup endpoint and UI + +Add `GET /api/geo/lookup/{ip}` to `backend/app/routers/geo.py`. The endpoint checks whether the IP is currently banned (and in which jails), retrieves its ban history (count, timestamps, jails), and fetches enriched info (country, ASN, RIR) from the geo service. On the frontend, create an IP Lookup section in the Jails area where the user can enter any IP and see all this information. See [Features.md § 5 (IP Lookup)](Features.md). + +### 6.8 Implement the ignore list (whitelist) endpoints and UI + +Add endpoints to `backend/app/routers/jails.py` for managing ignore lists: +- `GET /api/jails/{name}/ignoreip` — get the ignore list for a jail. +- `POST /api/jails/{name}/ignoreip` — add an IP or network to a jail's ignore list. +- `DELETE /api/jails/{name}/ignoreip` — remove an IP from the ignore list. +- `POST /api/jails/{name}/ignoreself` — toggle the "ignore self" option. + +On the frontend, add an "IP Whitelist" section to the jail detail page showing the ignore list with add/remove controls. See [Features.md § 5 (IP Whitelist)](Features.md). + +### 6.9 Write tests for jail and ban features + +Test jail listing with mocked socket responses, jail detail parsing, start/stop/reload commands, ban and unban execution, currently-banned list retrieval, IP lookup with and without ban history, and ignore list operations. Ensure all socket interactions are mocked. + +--- + +## Stage 7 — Configuration View + +This stage lets users inspect and edit fail2ban configuration directly from the web interface. + +### 7.1 Implement the config service + +Build `backend/app/services/config_service.py`. It reads the active fail2ban configuration by querying the daemon for jail settings, filter regex patterns, and global parameters. It also writes configuration changes by sending the appropriate set commands through the socket (or by editing config files and triggering a reload, depending on what fail2ban supports for each setting). The service must validate regex patterns before applying them — attempting to compile each pattern and returning a clear error if it is invalid. See [Features.md § 6 (View Configuration, Edit Configuration)](Features.md). + +### 7.2 Implement the config router + +Create `backend/app/routers/config.py`: +- `GET /api/config/jails` — list all jails with their current configuration. +- `GET /api/config/jails/{name}` — full configuration for a single jail (filter, regex, dates, actions, escalation). +- `PUT /api/config/jails/{name}` — update a jail's configuration (ban time, max retries, enabled, regex patterns, date pattern, DNS mode, escalation settings). +- `GET /api/config/global` — global fail2ban settings. +- `PUT /api/config/global` — update global settings. +- `POST /api/config/reload` — reload fail2ban to apply changes. + +Define models in `backend/app/models/config.py`. Return validation errors before saving. See [Architekture.md § 2.2 (Routers)](Architekture.md). + +### 7.3 Implement log observation endpoints + +Add endpoints for registering new log files that fail2ban should monitor. The user needs to specify a log file path, one or more failure-detection regex patterns, a jail name, and basic jail settings. Include a preview endpoint that reads the specified log file and tests the provided regex against its contents, returning matching lines so the user can verify the pattern before saving. See [Features.md § 6 (Add Log Observation)](Features.md). + +### 7.4 Implement the regex tester endpoint + +Add `POST /api/config/regex-test` to the config router. It accepts a sample log line and a fail regex pattern, attempts to match them, and returns whether the pattern matched along with any captured groups highlighted by position. This is a stateless utility endpoint. See [Features.md § 6 (Regex Tester)](Features.md). + +### 7.5 Implement server settings endpoints + +Create `backend/app/routers/server.py`: +- `GET /api/server/settings` — current log level, log target, syslog socket, DB path, purge age, max matches. +- `PUT /api/server/settings` — update server-level settings. +- `POST /api/server/flush-logs` — flush and re-open log files. + +Delegate to `backend/app/services/server_service.py`. See [Features.md § 6 (Server Settings)](Features.md). + +### 7.6 Build the configuration page (frontend) + +Create `frontend/src/pages/ConfigPage.tsx`. The page should show all jails with their current settings in a readable format. Each jail section expands to show filter regex, ignore regex, date pattern, actions, and escalation settings. Provide inline editing: clicking a value turns it into an editable field. Add/remove buttons for regex patterns. A "Save" button persists changes and optionally triggers a reload. Show validation errors inline. Use Fluent UI `Accordion`, `Input`, `Textarea`, `Switch`, and `Button`. See [Features.md § 6](Features.md) and [Web-Design.md](Web-Design.md). + +### 7.7 Build the regex tester UI (frontend) + +Add a "Regex Tester" section to the configuration page (or as a dialog/panel). Two input fields: one for a sample log line, one for the regex pattern. On every change (debounced), call the regex-test endpoint and display the result — whether it matched, and highlight the matched groups. Use monospace font for both inputs. See [Features.md § 6 (Regex Tester)](Features.md). + +### 7.8 Build the server settings UI (frontend) + +Add a "Server Settings" section to the configuration page. Display current values for log level, log target, syslog socket, DB path, purge age, and max matches. Provide dropdowns for log level and log target, text inputs for paths and numeric values. Include a "Flush Logs" button. See [Features.md § 6 (Server Settings)](Features.md). + +### 7.9 Write tests for configuration features + +Test config read and write operations with mocked fail2ban responses, regex validation (valid and invalid patterns), the regex tester with matching and non-matching inputs, and server settings read/write. Verify that changes are only applied after validation passes. + +--- + +## Stage 8 — World Map View + +A geographical visualisation of ban activity. This stage depends on the geo service from Stage 5 and the ban data pipeline from Stage 5. + +### 8.1 Implement the map data endpoint + +Add `GET /api/dashboard/bans/by-country` to the dashboard router. It accepts the same time-range parameter as the ban list endpoint. It queries bans in the selected window, enriches them with geo data, and returns an aggregated count per country (ISO country code → ban count). Also return the full ban list so the frontend can display the companion table. See [Features.md § 4](Features.md). + +### 8.2 Build the world map component (frontend) + +Create `frontend/src/components/WorldMap.tsx`. Render a full world map with country outlines only — no fill colours, no satellite imagery. For each country with bans, display the ban count centred inside the country's borders. Countries with zero bans remain blank. Consider a lightweight SVG-based map library or a TopoJSON/GeoJSON world outline rendered with D3 or a comparable tool. The map must be interactive: clicking a country filters the companion access list. Include the same time-range selector as the dashboard. See [Features.md § 4](Features.md). + +### 8.3 Build the map page (frontend) + +Create `frontend/src/pages/MapPage.tsx`. Compose the time-range selector, the `WorldMap` component, and an access list table below. When a country is selected on the map, the table filters to show only entries from that country. Clicking the map background (or a "Clear filter" button) removes the country filter. Create `frontend/src/hooks/useMapData.ts` to fetch and manage the aggregated data. See [Features.md § 4](Features.md). + +### 8.4 Write tests for the map data endpoint + +Test aggregation correctness: multiple bans from the same country should be summed, unknown countries should be handled gracefully, and empty time ranges should return an empty map object. + +--- + +## Stage 9 — Ban History + +This stage exposes historical ban data from the fail2ban database for forensic exploration. + +### 9.1 Implement the history service + +Build `backend/app/services/history_service.py`. Query the fail2ban database for all past ban records (not just currently active ones). Support filtering by jail, IP address, and time range. Compute ban count per IP to identify repeat offenders. Provide a per-IP timeline method that returns every ban event for a given IP: which jail triggered it, when it started, how long it lasted, and any matched log lines stored in the database. See [Features.md § 7](Features.md). + +### 9.2 Implement the history router + +Create `backend/app/routers/history.py`: +- `GET /api/history` — paginated list of all historical bans with filters (jail, IP, time range). Returns time, IP, jail, duration, ban count, country. +- `GET /api/history/{ip}` — per-IP detail: full ban timeline, total failures, matched log lines. + +Define models in `backend/app/models/history.py`. Enrich results with geo data. See [Architekture.md § 2.2](Architekture.md). + +### 9.3 Build the history page (frontend) + +Create `frontend/src/pages/HistoryPage.tsx`. Display a `DataGrid` table of all past bans with columns for time, IP (monospace), jail, ban duration, ban count, and country. Add filter controls above the table: a jail dropdown, an IP search input, and the standard time-range selector. Highlight rows with high ban counts to flag repeat offenders. Clicking an IP row navigates to a per-IP detail view showing the full ban timeline and aggregated failures. See [Features.md § 7](Features.md). + +### 9.4 Write tests for history features + +Test history queries with various filters, per-IP timeline construction, ban count computation, and edge cases (IP with no history, jail that no longer exists). + +--- + +## Stage 10 — External Blocklist Importer + +This stage adds the ability to automatically download and apply external IP blocklists on a schedule. + +### 10.1 Implement the blocklist repository + +Build `backend/app/repositories/blocklist_repo.py` and `backend/app/repositories/import_log_repo.py`. The blocklist repo persists blocklist source definitions (name, URL, enabled flag) in the application database. The import log repo records every import run with timestamp, source URL, IPs imported, IPs skipped, and any errors encountered. See [Architekture.md § 2.2 (Repositories)](Architekture.md). + +### 10.2 Implement the blocklist service + +Build `backend/app/services/blocklist_service.py`. It manages blocklist source CRUD (add, edit, remove, toggle enabled). For the actual import: download each enabled source URL via aiohttp, validate every entry as a well-formed IP or CIDR range using the `ipaddress` module, skip malformed lines gracefully, and apply valid IPs as bans through fail2ban (in a dedicated blocklist jail) or via iptables. If using iptables, flush the chain before re-populating. Log every step with structlog. Record import results through the import log repository. Handle unreachable URLs by logging the error and continuing with remaining sources. See [Features.md § 8](Features.md). + +### 10.3 Implement the blocklist import scheduled task + +Create `backend/app/tasks/blocklist_import.py` — an APScheduler job that runs the blocklist service import at the configured schedule. The default is daily at 03:00. The schedule should be configurable through the blocklist service (saved in the app database). See [Features.md § 8 (Schedule)](Features.md). + +### 10.4 Implement the blocklist router + +Create `backend/app/routers/blocklist.py`: +- `GET /api/blocklists` — list all blocklist sources with their status. +- `POST /api/blocklists` — add a new source. +- `PUT /api/blocklists/{id}` — edit a source (name, URL, enabled). +- `DELETE /api/blocklists/{id}` — remove a source. +- `GET /api/blocklists/{id}/preview` — download and display a sample of the blocklist contents. +- `POST /api/blocklists/import` — trigger a manual import immediately ("Run Now"). +- `GET /api/blocklists/schedule` — get the current schedule and next run time. +- `PUT /api/blocklists/schedule` — update the schedule. +- `GET /api/blocklists/log` — paginated import log, filterable by source and date range. + +Define models in `backend/app/models/blocklist.py`. See [Architekture.md § 2.2](Architekture.md). + +### 10.5 Build the blocklist management page (frontend) + +Create `frontend/src/pages/BlocklistPage.tsx`. Display a list of blocklist sources as cards or rows showing name, URL, enabled toggle, and action buttons (edit, delete, preview). Add a form to create or edit a source. Show the schedule configuration with a simple time-and-frequency picker (no raw cron) — dropdowns for frequency preset and a time input. Include a "Run Now" button and a display of last import time and next scheduled run. Below, show the import log as a table (timestamp, source, IPs imported, IPs skipped, errors) with filters. If the most recent import had errors, show a warning badge in the navigation. See [Features.md § 8](Features.md). + +### 10.6 Write tests for blocklist features + +Test source CRUD, import with valid/invalid entries, schedule update, manual import trigger, import log persistence, and error handling when a URL is unreachable. Mock all HTTP calls. + +--- + +## Stage 11 — Polish, Cross-Cutting Concerns & Hardening + +This final stage covers everything that spans multiple features or improves the overall quality of the application. + +### 11.1 Implement connection health indicator + +Add a persistent connection-health indicator visible on every page (part of the `MainLayout`). When the fail2ban server becomes unreachable, show a clear warning bar at the top of the interface. When it recovers, dismiss the warning. The indicator reads from the cached health status maintained by the background task from Stage 4. See [Features.md § 9](Features.md). + +### 11.2 Add timezone awareness + +Ensure all timestamps displayed in the frontend respect the timezone configured during setup. Store all dates in UTC on the backend. Convert to the user's configured timezone on the frontend before display. Create a `formatDate` utility in `frontend/src/utils/` that applies the configured timezone and format. See [Features.md § 9](Features.md). + +### 11.3 Add responsive layout polish + +Review every page against the breakpoint table in [Web-Design.md § 4](Web-Design.md). Ensure the sidebar collapses correctly on small screens, tables scroll horizontally instead of breaking, cards stack vertically, and no content overflows. Test at 320 px, 640 px, 1024 px, and 1920 px widths. + +### 11.4 Add loading and error states + +Every page and data-fetching component must handle three states: loading (show Fluent UI `Spinner` or skeleton shimmer), error (show a `MessageBar` with details and a retry action), and empty (show an informational message). Remove bare spinners that persist longer than one second — replace them with skeleton screens as required by [Web-Design.md § 6](Web-Design.md). + +### 11.5 Implement reduced-motion support + +Honour the `prefers-reduced-motion` media query. When detected, disable all non-essential animations (tab transitions, row slide-outs, panel fly-ins) and replace them with instant state changes. See [Web-Design.md § 6 (Motion Rules)](Web-Design.md). + +### 11.6 Add accessibility audit + +Verify WCAG 2.1 AA compliance across the entire application. All interactive elements must be keyboard-accessible. All Fluent UI components include accessibility by default, but custom components (world map, regex tester highlight) need manual `aria-label` and role attributes. Ensure colour is never the sole indicator of status — combine with icons or text labels. See [Web-Design.md § 1](Web-Design.md). + +### 11.7 Add structured logging throughout + +Review every service and task to confirm that all significant operations are logged with structlog and contextual key-value pairs. Log ban/unban actions, config changes, blocklist imports, authentication events, and health transitions. Never log passwords, session tokens, or other secrets. See [Backend-Development.md § 7](Backend-Development.md). + +### 11.8 Add global error handling + +Register FastAPI exception handlers in `main.py` that map all custom domain exceptions to HTTP status codes with structured error bodies. Ensure no unhandled exception ever returns a raw 500 with a stack trace to the client. Log all errors with full context before returning the response. See [Backend-Development.md § 8](Backend-Development.md). + +### 11.9 Final test pass and coverage check + +Run the full test suite. Ensure all tests pass. Check coverage: aim for over 80 % line coverage overall, with 100 % on critical paths (auth, banning, scheduled imports). Add missing tests where coverage is below threshold. Ensure `ruff`, `mypy --strict`, and `tsc --noEmit` all pass with zero errors. See [Backend-Development.md § 9](Backend-Development.md) and [Web-Development.md § 1](Web-Development.md). diff --git a/Docs/Web-Design.md b/Docs/Web-Design.md new file mode 100644 index 0000000..bfc1ac0 --- /dev/null +++ b/Docs/Web-Design.md @@ -0,0 +1,406 @@ +# Frontend Design — Rules & Guidelines + +Rules and conventions every designer must follow when working on BanGUI. This document defines the visual language, component usage, and design principles for the entire application. Read this before creating your first mockup or writing your first style. + +**Design system:** [Microsoft Fluent UI](https://developer.microsoft.com/en-us/fluentui#/get-started/web) + +--- + +## 1. Design Philosophy + +- **Clarity over decoration.** Every visual element must serve a purpose. If it does not help the user read, navigate, or act — remove it. +- **Consistency over creativity.** Reuse existing Fluent UI components and patterns before inventing new ones. A familiar interface lowers cognitive load. +- **Content first.** BanGUI is a data-heavy monitoring tool. Design around the data — tables, charts, status indicators — not around ornamental chrome. +- **Quiet until necessary.** The default state of the UI should feel calm and neutral. Reserve colour, motion, and elevation for moments that genuinely need attention (errors, warnings, active bans). +- **Accessible by default.** Every design decision must pass WCAG 2.1 AA. Colour alone must never be the only way to convey information. + +--- + +## 2. Theming & Colour + +All colour decisions go through the Fluent UI theme system. Never hard-code hex values in components. + +### Theme Slots + +Use **semantic colour slots** from `ISemanticColors` wherever possible. Semantic slots describe *intent* rather than *appearance*, which means the UI adapts automatically when the theme changes. + +| Intent | Semantic slot | Example usage | +|---|---|---| +| Primary action | `themePrimary` | Primary buttons, active nav items, links | +| Danger / error | `errorText`, `errorIcon` | Ban warnings, failed connections, validation errors | +| Success | `successIcon` (extend with custom slot) | Successful unban confirmation, server online status | +| Warning | Shared colour `yellow` / `yellowDark` | Blocklist import warnings, approaching limits | +| Neutral text | `neutralPrimary` | Body copy, table cell text | +| Secondary text | `neutralSecondary` | Metadata, timestamps, captions | +| Disabled | `disabledText`, `disabledBackground` | Inactive controls, unavailable jails | +| Surface | `bodyBackground`, `bodyStandoutBackground` | Page canvas, sidebar, card backgrounds | + +### Custom Theme + +BanGUI uses a **single custom theme** generated with the [Fluent UI Theme Designer](https://aka.ms/themedesigner). The theme is defined once in code and consumed everywhere via `ThemeProvider`. + +- The primary colour must have a **contrast ratio of at least 4.5 : 1** against `white` for text and **3 : 1** for large text and UI elements. +- Provide a **dark theme variant** alongside the default light theme. Both must share the same semantic slot names — only the palette values differ. +- Never reference Fluent UI palette slots (`themeDarker`, `neutralLight`, etc.) directly in components. Always go through semantic slots so theme switching works seamlessly. + +### Colour Rules + +- **Never use inline hex or RGB values.** Always reference theme tokens (`theme.palette.*` or `theme.semanticColors.*`). +- Use the **9-step theme colour ramp** (`themeLighterAlt` through `themeDarker`) for subtle tints and shades within the primary hue. +- Use the **14-step neutral ramp** (`white` through `black`) for backgrounds, borders, and text hierarchy. +- Reserve the **shared accent colours** (red, yellow, green, blue) strictly for semantic meaning: errors, warnings, success, informational. Never use them decoratively. +- When two adjacent regions need separation, prefer a **1 px `neutralLight` border** or a subtle background shift (`bodyStandoutBackground`) over a heavy line. + +--- + +## 3. Typography + +Use the Fluent UI type ramp exclusively. Do not introduce custom font sizes. + +### Type Ramp + +| Token | Size | Weight | Usage in BanGUI | +|---|---|---|---| +| `FontSizes.size28` | 28 px | Semibold (600) | Page titles — "Ban Overview", "Jail Management" | +| `FontSizes.size20` | 20 px | Semibold (600) | Section headers — "Ban List", "Server Settings" | +| `FontSizes.size16` | 16 px | Semibold (600) | Card titles, panel headers, sub-section labels | +| `FontSizes.size14` | 14 px | Regular (400) | Body text, table cell content, form labels, buttons | +| `FontSizes.size12` | 12 px | Regular (400) | Metadata: timestamps, IP geolocation tags, ban counts, tooltips | +| `FontSizes.size10` | 10 px | Regular (400) | Badges, disclaimer text, chart axis labels (use sparingly) | + +### Typography Rules + +- **Font family** — Use the Fluent default (`Segoe UI`, with the standard fallback stack). Never override the font family unless a monospace context demands it (log output, regex patterns, IP addresses — use `Consolas, "Courier New", monospace` for those). +- **Weight** — Only two weights in regular UI: **Regular (400)** for body text and **Semibold (600)** for headings and emphasis. Use **Bold (700)** only in exceptional data visualisation contexts. +- **Line height** — Follow the recommended line heights from the Fluent type ramp (e.g., 14 px text → 20 px line-height, 20 px text → 28 px line-height). +- **Text colour hierarchy** — Use `neutralPrimary` for primary text, `neutralSecondary` for secondary/supporting text, and `neutralTertiary` only for placeholder text. +- **Alignment** — Left-align all text by default. Centre-align only short labels inside cards or stat badges. Never right-align body text. +- **Truncation** — Long text (IP addresses, log paths, regex patterns) must truncate with an ellipsis and show the full content in a tooltip on hover. +- **Monospace contexts** — IP addresses, regex patterns, log paths, code snippets, and jail filter expressions must always render in monospace for readability. + +--- + +## 4. Layout & Spacing + +### Grid + +- Use a **12-column responsive grid**. In Fluent UI React, prefer CSS Grid or `Stack` for layout rather than Fabric Core grid classes. +- The main content area sits next to a **fixed-width side navigation** (240 px collapsed to 48 px on small screens). +- Maximum content width: **1440 px**, centred on ultra-wide monitors. + +### Breakpoints + +Follow the Fluent UI breakpoint scale: + +| Name | Range | Layout behaviour | +|---|---|---| +| Small | 320 – 479 px | Single column, collapsed nav, stacked cards | +| Medium | 480 – 639 px | Single column, optional side nav overlay | +| Large | 640 – 1023 px | Two columns, side nav visible | +| Extra large | 1024 – 1365 px | Full layout, side nav + main + optional aside | +| XX-large | 1366 – 1919 px | Comfortable full layout | +| XXX-large | 1920 px + | Max-width content, extra breathing room | + +### Spacing Scale + +Use the Fluent **4 px base unit** for all spacing. Common values: + +| Token | Value | Usage | +|---|---|---| +| `s2` | 4 px | Inline spacing between icon and label | +| `s1` | 8 px | Padding inside compact elements (badges, chips) | +| `m` | 16 px | Standard padding inside cards and panels | +| `l1` | 20 px | Gap between table rows / list items | +| `l2` | 32 px | Margin between sections on a page | +| `xl` | 48 px | Top margin for page content below the header | + +### Spacing Rules + +- **Never use arbitrary pixel values.** Always snap to the 4 px grid (4, 8, 12, 16, 20, 24, 32, 40, 48…). +- Padding inside a card or panel: **16 px** on all sides. +- Gap between cards in a grid: **16 px**. +- Gap between a page title and the first content block: **24 px**. +- Table cell padding: **12 px horizontal**, **8 px vertical**. + +--- + +## 5. Elevation & Depth + +Use Fluent UI depth levels to communicate layering. Do not create custom `box-shadow` values. + +| Level | Token | Usage in BanGUI | +|---|---|---| +| **Depth 4** | `Depths.depth4` | Cards, grid tiles, stat summaries on the dashboard | +| **Depth 8** | `Depths.depth8` | Command bars, dropdown menus, filter popups | +| **Depth 16** | `Depths.depth16` | Tooltips, hover cards (IP info popup), teaching callouts | +| **Depth 64** | `Depths.depth64` | Modal dialogs (ban/unban confirmation), side panels | + +### Elevation Rules + +- A surface's depth must **match its semantic importance**. A card sits at depth 4; a dialog demanding user action sits at depth 64. +- The **page canvas has zero elevation** — it is the baseline. +- Only **one modal level** may be visible at a time. Never stack dialogs. +- When a dropdown or popup opens, the rest of the interface should receive an **overlay scrim** (`rgba(0, 0, 0, 0.4)`) only for depth-64 surfaces (modals). Lighter popups (depth 8–16) dismiss on outside click without a scrim. + +--- + +## 6. Motion & Animation + +All animations follow Fluent motion principles. Motion should feel natural, purposeful, and quick. + +### Timing + +| Duration | Token | Usage | +|---|---|---| +| 100 ms | `MotionDurations.duration1` | Micro-interactions: button press, checkbox toggle | +| 200 ms | `MotionDurations.duration2` | Fade in/out of tooltips, small element transitions | +| 300 ms | `MotionDurations.duration3` | Panel slide-in, page transitions, list reordering | +| 400 ms | `MotionDurations.duration4` | Large surface entrance (full-screen modals, first-load hero) | + +### Easing + +| Curve | Token | When to use | +|---|---|---| +| Decelerate | `MotionTimings.decelerate` | Elements entering the view (slide in, fade in) | +| Accelerate | `MotionTimings.accelerate` | Elements leaving the view (slide out, fade out) | +| Standard | `MotionTimings.standard` | Elements that reposition within the view | +| Linear | `MotionTimings.linear` | Opacity-only transitions (fade out before a drill-in) | + +### Animation Patterns + +| Interaction | Pattern | Details | +|---|---|---| +| Opening a jail detail panel | **Slide right in** | Panel slides from right edge, content fades in after 100 ms delay | +| Banning / unbanning an IP | **Delete & Slide** | Row fades out (300 ms decelerate), remaining rows slide up (300 ms) | +| Switching dashboard tabs | **Tabs & Pivots** | Active indicator slides (300 ms), outgoing content slides out, incoming slides in with 100 ms delay | +| Navigating to a sub-page | **Drill In** | Old content fades out (100 ms linear), new content scales down in (300 ms decelerate) | + +### Motion Rules + +- **Never animate something the user did not trigger.** Auto-refreshing data should appear without animation — just update in place. +- **Respect `prefers-reduced-motion`.** When the OS or browser signals reduced motion, disable all non-essential animations and replace them with instant state changes. +- **No loading spinners longer than 1 second without explanation.** If a network call exceeds 1 s, show a skeleton screen or a contextual loading message, not just a spinner. +- Keep looping animations (pulsing status dots, progress bars) subtle and small-area. Never animate large surfaces continuously. + +--- + +## 7. Iconography + +- Use the **Fluent UI icon set** (`@fluentui/react-icons` or the Fabric Core icon font). Do not introduce third-party icon libraries. +- Icon size follows the adjacent text: **16 px** icons next to 14 px body text, **20 px** icons next to 16–20 px headers. +- Icons must always have a **text label** or an `aria-label`. Icon-only buttons are acceptable only in toolbars and compact table rows, and they must have a tooltip. +- Use **outline** icon variants by default. Switch to **filled** only for active or selected states (e.g., filled star for a favourited jail). +- Icon colour inherits from the text colour of its context. Override only for semantic purposes (red icon for errors, green for success). + +--- + +## 8. Component Usage + +Use Fluent UI React components as the building blocks. The following mapping shows which component to use for each BanGUI feature area. + +### Navigation + +| Element | Fluent component | Notes | +|---|---|---| +| Side navigation | `Nav` | Persistent on large screens, collapsible on small. Groups: Dashboard, Map, Jails, Config, History, Blocklists. | +| Breadcrumbs | `Breadcrumb` | Show on detail pages (Jail > sshd, History > IP detail). | +| Page tabs | `Pivot` | Dashboard (Ban List / Access List), Map (Map / Access List). | + +### Data Display + +| Element | Fluent component | Notes | +|---|---|---| +| Data tables | `DetailsList` | All ban tables, jail overviews, history tables. Enable column sorting, selection, and shimmer loading. | +| Stat cards | `DocumentCard` or custom `Stack` card | Dashboard status bar — server status, total bans, active jails. Use `Depth 4`. | +| Status indicators | `Badge` / `Icon` + colour | Server online/offline, jail running/stopped/idle. | +| Country labels | Monospaced text + flag emoji or icon | Geo data next to IP addresses. | + +### Forms & Actions + +| Element | Fluent component | Notes | +|---|---|---| +| Primary actions | `PrimaryButton` | "Ban IP", "Save Configuration", "Run Import Now". | +| Secondary actions | `DefaultButton` | "Cancel", "Reset", "Clear Filters". | +| Danger actions | `PrimaryButton` with danger styling | "Unban All", "Flush Chain". Red theme override on `themePrimary`. | +| Text inputs | `TextField` | IP address entry, regex pattern input, search fields. | +| Dropdowns | `Dropdown` | Jail selector, time-range presets, log level picker. | +| Toggles | `Toggle` | Enable/disable jail, enable blocklist source, ignore-self toggle. | +| Confirmations | `Dialog` | Confirm ban, confirm unban-all, confirm delete actions. Always require explicit user action. | + +### Feedback + +| Element | Fluent component | Notes | +|---|---|---| +| Success messages | `MessageBar` (success) | "IP 1.2.3.4 has been banned in jail sshd." | +| Error messages | `MessageBar` (error) | "Failed to connect to fail2ban server." | +| Warning messages | `MessageBar` (warning) | "Blocklist import encountered 12 invalid entries." | +| Loading states | `Shimmer` | Apply to `DetailsList` rows and stat cards while data loads. | +| Empty states | Custom illustration + text | "No bans recorded in the last 24 hours." Centre on the content area. | +| Tooltips | `Tooltip` / `TooltipHost` | Full IP info on hover, full regex on truncated text, icon-only button labels. | + +--- + +## 9. Tables & Data Grids + +Tables are the primary UI element in BanGUI. They must be treated with extreme care. + +- Use Fluent UI's `DetailsList` for all tabular data. +- **Column widths:** Give the widest anticipated content enough room. IP addresses need ~140 px, timestamps ~180 px, country codes ~80 px. Use `minWidth` and `maxWidth` on each column. +- **Row density:** Use `compact` mode for tables with many rows (ban lists, history) and `normal` mode for tables with fewer rows (jail overview, blocklist sources). +- **Sorting:** Every column with comparable data must be sortable. Default sort: newest first for time-based tables. +- **Selection:** Table rows for actionable entities (banned IPs, jails) must support single selection that highlights the row and enables contextual actions in a command bar above. +- **Shimmer loading:** When data is loading, show shimmer placeholder rows matching the expected column layout. Never show a blank table body. +- **Empty state:** When the table has zero rows, display a centred message and icon inside the table frame (e.g., "No bans in this time range"). +- **Sticky header:** Table headers must stick to the top of the scrollable area so column labels are always visible. +- **Zebra striping:** Do not use alternating row colours. Fluent UI relies on hover and selection highlights instead. +- **Pagination vs. virtual scrolling:** For tables that may exceed 100 rows, use virtual scrolling (`DetailsList` supports this natively). Avoid traditional pagination. + +--- + +## 10. Cards & Stat Blocks + +The dashboard uses cards to display key figures (server status, total bans, active jails). + +- Each card is a contained surface at **Depth 4** with **16 px padding** on all sides. +- Card layout: **icon or status dot** (left) + **large numeric value** (`FontSizes.size28`, semibold) + **label** (`FontSizes.size12`, `neutralSecondary`). +- Cards arrange in a horizontal row using `Stack` with `horizontal` tokens and `16 px` gap. +- On small screens, cards stack vertically in a single column. +- **Do not put actions inside stat cards.** They are read-only summaries. Actions belong in context menus or command bars. + +--- + +## 11. World Map View + +- The map renders country outlines only — **no fill colours, no satellite imagery, no terrain shading**. +- Countries with banned IPs display a **count badge** centred inside the country polygon. Use `FontSizes.size14` semibold, `themePrimary` colour. +- Countries with zero bans remain completely blank — no label, no tint. +- On hover: country region gets a subtle `neutralLighterAlt` fill. On click: fill shifts to `themeLighterAlt` and the companion table below filters to that country. +- The map must have a **light neutral border** (`neutralLight`) around its container, at **Depth 4**. +- Time-range selector above the map uses `Pivot` with quick presets (24 h, 7 d, 30 d, 365 d). + +--- + +## 12. Forms & Inputs + +- Use `TextField` with a **visible label above** the input (not a floating label). +- Every text input must show a `description` or `placeholder` that illustrates the expected format (e.g., `192.168.1.0/24`). +- Validation errors appear **below the input** using the built-in `errorMessage` prop — red text, small size (`FontSizes.size12`). +- Group related fields inside a bordered `Stack` or a `GroupedList` with a section header. +- Action buttons sit at the **bottom-right** of a form group, with primary on the right and secondary (Cancel) on the left. +- For the Regex Tester: use a side-by-side layout — sample log line on the left, regex input on the right, match result highlighted below. Monospace font for both fields. + +--- + +## 13. Feedback & Status + +### Server Status Bar + +- A thin horizontal bar at the very top of the content area (below the nav header). +- **Online:** green dot + "fail2ban running — v0.11.2 — 14 jails — 231 total bans". Use `FontSizes.size12`, `neutralSecondary`. +- **Offline / unreachable:** red dot + "fail2ban server unreachable" in `errorText` colour. Bar background shifts to a very light red tint. +- This bar is **non-dismissible** and always visible. + +### Inline Feedback + +- After a successful action (ban, unban, save config): show a `MessageBar` of type `success` **at the top of the current content area**, auto-dismiss after 5 seconds. +- After a failed action: show a `MessageBar` of type `error`, **not auto-dismissed** — the user must close it manually. +- Warnings (import issues, validation hints) use `MessageBar` of type `warning`, not auto-dismissed. + +--- + +## 14. Dark Theme + +- Every screen must look correct in both light and dark themes. +- Design light theme first, then verify dark theme by swapping the palette in the Theme Designer. +- **Never assume white backgrounds.** Always use `bodyBackground` or `bodyStandoutBackground` from the theme. +- **Never use black text directly.** Always use `neutralPrimary`, which adapts to both themes. +- Images, illustrations, and the world map must have **transparent or theme-aware backgrounds** that do not create harsh rectangles in dark mode. +- Test contrast ratios in **both themes** — a colour that passes AA on white may fail on dark grey. + +--- + +## 15. Accessibility + +- **Colour contrast:** All text must meet WCAG 2.1 AA minimums (4.5 : 1 for normal text, 3 : 1 for large text and UI components). Use the [Fluent UI colour accessibility guide](https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/fabric-website/files/coloraccessibility_29sep2016.pdf) as reference. +- **Keyboard navigation:** Every interactive element must be reachable via Tab and operable with Enter or Space. Sidebar navigation, table row selection, dropdown menus — all must work without a mouse. +- **Focus indicators:** Use the default Fluent UI focus ring (2 px `themePrimary` outline). Never hide or override focus styles. +- **Screen readers:** Every icon-only button has an `aria-label`. Tables use `aria-sort` on sortable columns. Status indicators have `aria-live="polite"` regions so screen readers announce changes. +- **Reduced motion:** Wrap all custom animations in a `prefers-reduced-motion` media query and provide an instant fallback. +- **Touch targets:** All interactive elements have a minimum tap target of **44 x 44 px** on touch devices (achieved via padding if the visible element is smaller). + +--- + +## 16. Design Tokens & File Organisation + +- All theme definitions live in a single file: `theme/appTheme.ts`. +- All spacing, sizing, and z-index constants live in `theme/tokens.ts`. +- Components consume tokens via the theme context — never import raw pixel values. +- When a design requires a value not already in the token set, **add it to the shared tokens file** and document its purpose. Never define a one-off constant inside a component. + +```ts +// theme/appTheme.ts +import { createTheme } from "@fluentui/react"; + +export const lightTheme = createTheme({ + palette: { + themePrimary: "#0078d4", + themeDarkAlt: "#106ebe", + // ... generated via Theme Designer + }, +}); + +export const darkTheme = createTheme({ + palette: { + themePrimary: "#2b88d8", + themeDarkAlt: "#3aa0f3", + // ... + }, +}); +``` + +```ts +// theme/tokens.ts +export const spacing = { + xs: 4, + s: 8, + m: 16, + l: 24, + xl: 32, + xxl: 48, +} as const; + +export const contentMaxWidth = 1440; +export const sideNavWidth = 240; +export const sideNavCollapsedWidth = 48; +``` + +--- + +## 17. Do-Not-Do List + +| Do | Do Not | +|---|---| +| Use Fluent UI components for every standard control | Build custom buttons, dropdowns, or modals from scratch | +| Reference theme tokens for all colours | Hard-code hex values like `#ff0000` in components | +| Follow the 4 px spacing grid | Use arbitrary pixel values (13 px, 7 px, 19 px) | +| Provide a tooltip for every icon-only button | Leave icons unlabelled and inaccessible | +| Use `Shimmer` for loading states | Show a blank screen or a standalone spinner with no context | +| Design for both light and dark themes | Default to white backgrounds assuming light mode only | +| Use `DetailsList` for all tabular data | Use raw HTML `` elements or a third-party data grid | +| Use semantic colour slots (`errorText`, `bodyBackground`) | Use descriptive palette slots (`red`, `neutralLight`) directly | +| Use `prefers-reduced-motion` for all custom animation | Force animation on users with motion sensitivities | +| Test with keyboard and screen reader before signing off | Assume mouse-only usage | + +--- + +## 18. References + +- [Fluent UI — Get Started](https://developer.microsoft.com/en-us/fluentui#/get-started/web) +- [Fluent UI — Typography](https://developer.microsoft.com/en-us/fluentui#/styles/web/typography) +- [Fluent UI — Colour Theme Slots](https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/theme-slots) +- [Fluent UI — Elevation](https://developer.microsoft.com/en-us/fluentui#/styles/web/elevation) +- [Fluent UI — Layout](https://developer.microsoft.com/en-us/fluentui#/styles/web/layout) +- [Fluent UI — Motion](https://developer.microsoft.com/en-us/fluentui#/styles/web/motion) +- [Fluent UI Theme Designer](https://aka.ms/themedesigner) +- [Colour Accessibility Guide (PDF)](https://res-1.cdn.office.net/files/fabric-cdn-prod_20230815.002/fabric-website/files/coloraccessibility_29sep2016.pdf) +- [WCAG 2.1 AA Guidelines](https://www.w3.org/WAI/WCAG21/quickref/) diff --git a/Docs/Web-Development.md b/Docs/Web-Development.md new file mode 100644 index 0000000..dc8f1f6 --- /dev/null +++ b/Docs/Web-Development.md @@ -0,0 +1,520 @@ +# Frontend Development — Rules & Guidelines + +Rules and conventions every frontend developer must follow. Read this before writing your first line of code. + +--- + +## 1. Language & Typing + +- **TypeScript** is mandatory — no plain JavaScript files (`.js`, `.jsx`) in the codebase. +- Use **strict mode** (`"strict": true` in `tsconfig.json`) — the project must compile with zero errors. +- Never use `any`. If a type is truly unknown, use `unknown` and narrow it with type guards. +- Prefer **interfaces** for object shapes that may be extended, **type aliases** for unions, intersections, and utility types. +- Every function must have explicit parameter types and return types — including React components (`React.FC` is discouraged; type props and return `JSX.Element` explicitly). +- Use `T | null` or `T | undefined` instead of `Optional` patterns — be explicit about nullability. +- Use `as const` for constant literals and enums where it improves type narrowness. +- Run `tsc --noEmit` in CI — the codebase must pass with zero type errors. + +```tsx +// Good +interface BanEntry { + ip: string; + jail: string; + bannedAt: string; + expiresAt: string | null; +} + +function BanRow({ ban }: { ban: BanEntry }): JSX.Element { + return ; +} + +// Bad — untyped, uses `any` +function BanRow({ ban }: any) { + return ; +} +``` + +--- + +## 2. Reusable Types + +- All **shared type definitions** live in a dedicated `types/` directory. +- Group types by domain: `types/ban.ts`, `types/jail.ts`, `types/auth.ts`, `types/api.ts`, etc. +- Import types using the `import type` syntax when the import is only used for type checking — this keeps the runtime bundle clean. +- Component-specific prop types may live in the same file as the component, but any type used by **two or more files** must move to `types/`. +- Never duplicate a type definition — define it once, import everywhere. +- Export API response shapes alongside their domain types so consumers always know what the server returns. + +```ts +// types/ban.ts +export interface Ban { + ip: string; + jail: string; + bannedAt: string; + expiresAt: string | null; + banCount: number; + country: string | null; +} + +export interface BanListResponse { + bans: Ban[]; + total: number; +} +``` + +```tsx +// components/BanTable.tsx +import type { Ban } from "../types/ban"; +``` + +--- + +## 3. Type Safety in API Calls + +- Every API call must have a **typed request** and **typed response**. +- Define response shapes as TypeScript interfaces in `types/` and cast the response through them. +- Use a **central API client** (e.g., a thin wrapper around `fetch` or `axios`) that returns typed data — individual components never call `fetch` directly. +- Validate or assert the response structure at the boundary when dealing with untrusted data; for critical flows, consider a runtime validation library (e.g., `zod`). +- API endpoint paths are **constants** defined in a single file (`api/endpoints.ts`) — never hard-code URLs in components. + +```ts +// api/client.ts +const BASE_URL = import.meta.env.VITE_API_URL ?? "/api"; + +async function get(path: string): Promise { + const response: Response = await fetch(`${BASE_URL}${path}`, { + credentials: "include", + }); + if (!response.ok) { + throw new ApiError(response.status, await response.text()); + } + return (await response.json()) as T; +} + +export const api = { get, post, put, del } as const; +``` + +```ts +// api/bans.ts +import type { BanListResponse } from "../types/ban"; +import { api } from "./client"; + +export async function fetchBans(hours: number): Promise { + return api.get(`/bans?hours=${hours}`); +} +``` + +--- + +## 4. Code Organization + +### Project Structure + +``` +frontend/ +├── public/ +├── src/ +│ ├── api/ # API client, endpoint definitions, per-domain request files +│ ├── assets/ # Static images, fonts, icons +│ ├── components/ # Reusable UI components (buttons, modals, tables, etc.) +│ ├── hooks/ # Custom React hooks +│ ├── layouts/ # Page-level layout wrappers (sidebar, header, etc.) +│ ├── pages/ # Route-level page components (one per route) +│ ├── providers/ # React context providers (auth, theme, etc.) +│ ├── theme/ # Fluent UI custom theme, tokens, and overrides +│ ├── types/ # Shared TypeScript type definitions +│ ├── utils/ # Pure helper functions, constants, formatters +│ ├── App.tsx # Root component, FluentProvider + router setup +│ ├── main.tsx # Entry point +│ └── vite-env.d.ts # Vite type shims +├── .eslintrc.cjs +├── .prettierrc +├── tsconfig.json +├── vite.config.ts +└── package.json +``` + +### Separation of Concerns + +- **Pages** handle routing and compose layout + components — they contain no business logic. +- **Components** are reusable, receive data via props, and emit changes via callbacks — they never call the API directly. +- **Hooks** encapsulate stateful logic, side effects, and API calls so components stay declarative. +- **API layer** handles all HTTP communication — components and hooks consume typed functions from `api/`, never raw `fetch`. +- **Types** are purely declarative — no runtime code in `types/` files. +- **Utils** are pure functions with no side effects and no React dependency. +- **Theme** contains exclusively Fluent UI custom token overrides and theme definitions — no component logic. + +--- + +## 5. UI Framework — Fluent UI React (v9) + +- **Fluent UI React Components v9** (`@fluentui/react-components`) is the only UI component library allowed — do not add alternative component libraries (Material UI, Chakra, Ant Design, etc.). +- Install via npm: + +```bash +npm install @fluentui/react-components @fluentui/react-icons +``` + +### FluentProvider + +- Wrap the entire application in `` at the root — this supplies the theme and design tokens to all Fluent components. +- The provider must sit above the router so every page inherits the theme. + +```tsx +// App.tsx +import { FluentProvider, webLightTheme } from "@fluentui/react-components"; +import { BrowserRouter } from "react-router-dom"; +import AppRoutes from "./AppRoutes"; + +function App(): JSX.Element { + return ( + + + + + + ); +} + +export default App; +``` + +### Theming & Design Tokens + +- Use the built-in themes (`webLightTheme`, `webDarkTheme`) as the base. +- Customise design tokens by creating a **custom theme** in `theme/` — never override Fluent styles with raw CSS. +- Reference tokens via the `tokens` object from `@fluentui/react-components` when writing `makeStyles` rules. +- If light/dark mode is needed, switch the `theme` prop on `FluentProvider` — never duplicate style definitions for each mode. + +```ts +// theme/customTheme.ts +import { createLightTheme, createDarkTheme } from "@fluentui/react-components"; +import type { BrandVariants, Theme } from "@fluentui/react-components"; + +const brandColors: BrandVariants = { + 10: "#020305", + // ... define brand colour ramp + 160: "#e8ebf9", +}; + +export const lightTheme: Theme = createLightTheme(brandColors); +export const darkTheme: Theme = createDarkTheme(brandColors); +``` + +### Styling with `makeStyles` (Griffel) + +- All custom styling is done via `makeStyles` from `@fluentui/react-components` — Fluent UI uses **Griffel** (CSS-in-JS with atomic classes) under the hood. +- Never use inline `style` props, global CSS, or external CSS frameworks for Fluent components. +- Co-locate styles in the same file as the component they belong to, defined above the component function. +- Use `mergeClasses` when combining multiple style sets conditionally. +- Reference Fluent **design tokens** (`tokens.colorBrandBackground`, `tokens.fontSizeBase300`, etc.) instead of hard-coded values — this ensures consistency and automatic theme support. + +```tsx +import { makeStyles, tokens, mergeClasses } from "@fluentui/react-components"; + +const useStyles = makeStyles({ + root: { + padding: tokens.spacingVerticalM, + backgroundColor: tokens.colorNeutralBackground1, + }, + highlighted: { + backgroundColor: tokens.colorPaletteRedBackground2, + }, +}); + +function BanCard({ isHighlighted }: BanCardProps): JSX.Element { + const styles = useStyles(); + return ( +
+ {/* ... */} +
+ ); +} +``` + +### Component Usage Rules + +- **Always** prefer Fluent UI components over plain HTML elements for interactive and presentational UI: `
{ban.ip}{ban.jail}
{ban.ip}
`, ``, ``, ``, ``, ``, ``, etc. +- Use `` for data-heavy tables (ban lists, jail lists) — it provides sorting, selection, and accessibility out of the box. +- Use Fluent UI `` for modals and confirmations — never build custom modal overlays. +- Use `@fluentui/react-icons` for all icons — do not mix icon libraries. +- Customise Fluent components only through their public API (props, slots, `makeStyles`) — never patch internal DOM or override internal class names. + +### Libraries you must NOT use alongside Fluent UI + +- `tailwindcss` — use `makeStyles` and design tokens. +- `styled-components` / `emotion` — Fluent UI uses Griffel; mixing CSS-in-JS runtimes causes conflicts. +- `@mui/*`, `antd`, `chakra-ui` — one design system only. +- Global CSS files that target Fluent class names — use `makeStyles` overrides. + +--- + +## 6. Component Rules + +- One component per file. The filename matches the component name: `BanTable.tsx` exports `BanTable`. +- Use **function declarations** for components — not arrow-function variables. +- Keep components **small and focused** — if a component exceeds ~150 lines, split it. +- Props are defined as an `interface` named `Props` in the same file (or imported from `types/` if shared). +- Destructure props in the function signature. +- Never mutate props or state directly — always use immutable update patterns. +- Avoid inline styles — use `makeStyles` from Fluent UI for all custom styling (see section 5). +- Supply a `key` prop whenever rendering lists — never use array indices as keys if the list can reorder. +- Prefer Fluent UI components (`Button`, `Table`, `Input`, …) over raw HTML elements for any interactive or styled element. + +```tsx +import { Table, TableBody, TableRow, TableCell, Button } from "@fluentui/react-components"; +import type { Ban } from "../types/ban"; + +interface BanTableProps { + bans: Ban[]; + onUnban: (ip: string) => void; +} + +function BanTable({ bans, onUnban }: BanTableProps): JSX.Element { + return ( +
+ + {bans.map((ban) => ( + + {ban.ip} + {ban.jail} + + + + + ))} + +
+ ); +} + +export default BanTable; +``` + +--- + +## 7. Hooks & State Management + +- Prefix custom hooks with `use` — e.g., `useBans`, `useAuth`, `useJails`. +- Each hook lives in its own file under `hooks/`. +- Use `useState` for local UI state, `useReducer` for complex state transitions. +- Use **React Context** sparingly — only for truly global concerns (auth, theme). Do not use context as a replacement for prop drilling one or two levels. +- Avoid `useEffect` for derived data — compute it during render or use `useMemo`. +- Always include the correct dependency arrays in `useEffect`, `useMemo`, and `useCallback`. Disable the ESLint exhaustive-deps rule **only** with a comment explaining why. +- Clean up side effects (subscriptions, timers, abort controllers) in the `useEffect` cleanup function. + +```tsx +// hooks/useBans.ts +import { useState, useEffect } from "react"; +import type { Ban } from "../types/ban"; +import { fetchBans } from "../api/bans"; + +interface UseBansResult { + bans: Ban[]; + loading: boolean; + error: string | null; +} + +function useBans(hours: number): UseBansResult { + const [bans, setBans] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const controller = new AbortController(); + + async function load(): Promise { + setLoading(true); + try { + const data = await fetchBans(hours); + setBans(data.bans); + setError(null); + } catch (err) { + if (!controller.signal.aborted) { + setError(err instanceof Error ? err.message : "Unknown error"); + } + } finally { + setLoading(false); + } + } + + load(); + return () => controller.abort(); + }, [hours]); + + return { bans, loading, error }; +} + +export default useBans; +``` + +--- + +## 8. Naming Conventions + +| Element | Convention | Example | +|---|---|---| +| Components | PascalCase | `BanTable`, `JailCard` | +| Component files | PascalCase `.tsx` | `BanTable.tsx` | +| Hooks | camelCase with `use` prefix | `useBans`, `useAuth` | +| Hook files | camelCase `.ts` | `useBans.ts` | +| Type / Interface | PascalCase | `BanEntry`, `JailListResponse` | +| Type files | camelCase `.ts` | `ban.ts`, `jail.ts` | +| Utility functions | camelCase | `formatDate`, `buildQuery` | +| Constants | UPPER_SNAKE_CASE | `MAX_RETRIES`, `API_BASE_URL` | +| makeStyles hooks | `useStyles` (file-scoped) | `const useStyles = makeStyles({…})` | +| makeStyles keys | camelCase slot names | `root`, `header`, `highlighted` | +| Directories | lowercase kebab‑case or camelCase | `components/`, `hooks/` | +| Boolean props/variables | `is`/`has`/`should` prefix | `isLoading`, `hasError` | + +--- + +## 9. Linting & Formatting + +- **ESLint** with the following plugins is required: + - `@typescript-eslint/eslint-plugin` — TypeScript-specific rules. + - `eslint-plugin-react` — React best practices. + - `eslint-plugin-react-hooks` — enforce rules of hooks. + - `eslint-plugin-import` — ordered and valid imports. +- **Prettier** handles all formatting — ESLint must not conflict with Prettier (use `eslint-config-prettier`). +- Format on save is expected — every developer must enable it in their editor. +- Run `eslint . --max-warnings 0` and `prettier --check .` in CI — zero warnings, zero formatting diffs. +- Line length: **100 characters** max. +- Strings: use **double quotes** (`"`). +- Semicolons: **always**. +- Trailing commas: **all** (ES5+). +- Indentation: **2 spaces**. +- No unused variables, no unused imports, no `@ts-ignore` without an accompanying comment. +- Import order (enforced by ESLint): React → third-party → aliases → relative, each group separated by a blank line. + +```jsonc +// .prettierrc +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} +``` + +--- + +## 10. Clean Code Principles + +- **Single Responsibility:** Every function, hook, and component does one thing well. +- **DRY (Don't Repeat Yourself):** Extract repeated JSX into components, repeated logic into hooks or utils. If you copy-paste, refactor. +- **KISS (Keep It Simple, Stupid):** Prefer the simplest solution that works. Avoid premature abstraction. +- **Meaningful Names:** Variable and function names describe **what**, not **how**. Avoid abbreviations (`btn` → `button`, `idx` → `index`) except universally understood ones (`id`, `url`, `ip`). +- **Small Functions:** If a function exceeds ~30 lines, it likely does too much — split it. +- **No Magic Numbers / Strings:** Extract constants with descriptive names. +- **Explicit over Implicit:** Favor clarity over cleverness. Code is written once and read many times. +- **No Dead Code:** Remove unused functions, commented-out blocks, and unreachable branches before committing. +- **Early Returns:** Reduce nesting by returning early from guard clauses. +- **Immutability:** Default to `const`. Use spread / `map` / `filter` instead of mutating arrays and objects. + +```tsx +// Bad — magic number, unclear name +if (data.length > 50) { ... } + +// Good +const MAX_VISIBLE_BANS = 50; +if (data.length > MAX_VISIBLE_BANS) { ... } +``` + +--- + +## 11. Error Handling + +- Wrap API calls in `try-catch` inside hooks — components should never see raw exceptions. +- Display user-friendly error messages — never expose stack traces or raw server responses in the UI. +- Use an **error boundary** (`ErrorBoundary` component) at the page level to catch unexpected render errors. +- Log errors to the console (or a future logging service) with sufficient context for debugging. +- Always handle the **loading**, **error**, and **empty** states for every data-driven component. + +--- + +## 12. Performance + +- Use `React.memo` only when profiling reveals unnecessary re-renders — do not wrap every component by default. +- Use `useMemo` and `useCallback` for expensive computations and stable callback references passed to child components — not for trivial values. +- Lazy-load route-level pages with `React.lazy` + `Suspense` to reduce initial bundle size. +- Avoid creating new objects or arrays inside render unless necessary — stable references prevent child re-renders. +- Keep bundle size in check — review dependencies before adding them and prefer lightweight alternatives. + +--- + +## 13. Accessibility + +- Use semantic HTML elements (`