Stage 6: jail management — backend service, routers, tests, and frontend

- jail_service.py: list/detail/control/ban/unban/ignore-list/IP-lookup
- jails.py router: 11 endpoints including ignore list management
- bans.py router: active bans, ban, unban
- geo.py router: IP lookup with geo enrichment
- models: Jail.actions, ActiveBan.country/.banned_at optional, GeoDetail
- 217 tests pass (40 service + 36 router + 141 existing), 76% coverage
- Frontend: types/jail.ts, api/jails.ts, hooks/useJails.ts
- JailsPage: jail overview table with controls, ban/unban forms,
  active bans table, IP lookup
- JailDetailPage: full detail, start/stop/idle/reload, patterns,
  ignore list management
This commit is contained in:
2026-03-01 14:09:02 +01:00
parent 9ac7f8d22d
commit ebec5e0f58
18 changed files with 5472 additions and 62 deletions

210
frontend/src/types/jail.ts Normal file
View File

@@ -0,0 +1,210 @@
/**
* TypeScript interfaces mirroring the backend jail Pydantic models.
*
* Backend sources:
* - `backend/app/models/jail.py`
* - `backend/app/models/ban.py` (ActiveBan / ActiveBanListResponse)
* - `backend/app/models/geo.py` (GeoDetail / IpLookupResponse)
*/
// ---------------------------------------------------------------------------
// Jail statistics
// ---------------------------------------------------------------------------
/**
* Live filter+actions counters for a single jail.
*
* Mirrors `JailStatus` from `backend/app/models/jail.py`.
*/
export interface JailStatus {
/** Number of IPs currently banned under this jail. */
currently_banned: number;
/** Total bans issued since fail2ban started. */
total_banned: number;
/** Current number of log-line matches that have not yet led to a ban. */
currently_failed: number;
/** Total log-line matches since fail2ban started. */
total_failed: number;
}
// ---------------------------------------------------------------------------
// Jail list (overview)
// ---------------------------------------------------------------------------
/**
* Lightweight snapshot of one jail for the overview table.
*
* Mirrors `JailSummary` from `backend/app/models/jail.py`.
*/
export interface JailSummary {
/** Machine-readable jail name (e.g. `"sshd"`). */
name: string;
/** Whether the jail is enabled in the configuration. */
enabled: boolean;
/** Whether fail2ban is currently monitoring the jail. */
running: boolean;
/** Whether the jail is in idle mode (monitoring paused). */
idle: boolean;
/** Backend type used for log access (e.g. `"systemd"`, `"polling"`). */
backend: string;
/** Observation window in seconds before a ban is triggered. */
find_time: number;
/** Duration of a ban in seconds (negative = permanent). */
ban_time: number;
/** Maximum log-line failures before a ban is issued. */
max_retry: number;
/** Live ban/failure counters, or `null` when unavailable. */
status: JailStatus | null;
}
/**
* Response from `GET /api/jails`.
*
* Mirrors `JailListResponse` from `backend/app/models/jail.py`.
*/
export interface JailListResponse {
/** All known jails. */
jails: JailSummary[];
/** Total number of jails. */
total: number;
}
// ---------------------------------------------------------------------------
// Jail detail
// ---------------------------------------------------------------------------
/**
* Full configuration and state of a single jail.
*
* Mirrors `Jail` from `backend/app/models/jail.py`.
*/
export interface Jail {
/** Machine-readable jail name. */
name: string;
/** Whether the jail is running. */
running: boolean;
/** Whether the jail is in idle mode. */
idle: boolean;
/** Backend type (systemd, polling, etc.). */
backend: string;
/** Log file paths monitored by this jail. */
log_paths: string[];
/** Fail-regex patterns used to identify offenders. */
fail_regex: string[];
/** Ignore-regex patterns used to whitelist log lines. */
ignore_regex: string[];
/** Date-pattern used for timestamp parsing, or empty string. */
date_pattern: string;
/** Log file encoding (e.g. `"UTF-8"`). */
log_encoding: string;
/** Action names attached to this jail. */
actions: string[];
/** Observation window in seconds. */
find_time: number;
/** Ban duration in seconds; negative means permanent. */
ban_time: number;
/** Maximum failures before ban is applied. */
max_retry: number;
/** Live counters, or `null` when not available. */
status: JailStatus | null;
}
/**
* Response from `GET /api/jails/{name}`.
*
* Mirrors `JailDetailResponse` from `backend/app/models/jail.py`.
*/
export interface JailDetailResponse {
/** Full jail configuration. */
jail: Jail;
/** Current ignore list (IPs / networks that are never banned). */
ignore_list: string[];
/** Whether the jail ignores the server's own IP addresses. */
ignore_self: boolean;
}
// ---------------------------------------------------------------------------
// Jail command response
// ---------------------------------------------------------------------------
/**
* Generic acknowledgement from jail control endpoints.
*
* Mirrors `JailCommandResponse` from `backend/app/models/jail.py`.
*/
export interface JailCommandResponse {
/** Human-readable result message. */
message: string;
/** Target jail name, or `"*"` for operations on all jails. */
jail: string;
}
// ---------------------------------------------------------------------------
// Active bans
// ---------------------------------------------------------------------------
/**
* A single currently-active ban entry.
*
* Mirrors `ActiveBan` from `backend/app/models/ban.py`.
*/
export interface ActiveBan {
/** Banned IP address. */
ip: string;
/** Jail that issued the ban. */
jail: string;
/** ISO 8601 UTC timestamp the ban started, or `null` when unavailable. */
banned_at: string | null;
/** ISO 8601 UTC timestamp the ban expires, or `null` for permanent bans. */
expires_at: string | null;
/** Number of times this IP has been banned before. */
ban_count: number;
/** ISO 3166-1 alpha-2 country code, or `null` when unknown. */
country: string | null;
}
/**
* Response from `GET /api/bans/active`.
*
* Mirrors `ActiveBanListResponse` from `backend/app/models/ban.py`.
*/
export interface ActiveBanListResponse {
/** List of all currently active bans. */
bans: ActiveBan[];
/** Total number of active bans. */
total: number;
}
// ---------------------------------------------------------------------------
// Geo / IP lookup
// ---------------------------------------------------------------------------
/**
* Geo-location information for an IP address.
*
* Mirrors `GeoDetail` from `backend/app/models/geo.py`.
*/
export interface GeoDetail {
/** ISO 3166-1 alpha-2 country code (e.g. `"DE"`), or `null`. */
country_code: string | null;
/** Country name (e.g. `"Germany"`), or `null`. */
country_name: string | null;
/** Autonomous System Number string (e.g. `"AS3320"`), or `null`. */
asn: string | null;
/** Organisation name associated with the IP, or `null`. */
org: string | null;
}
/**
* Response from `GET /api/geo/lookup/{ip}`.
*
* Mirrors `IpLookupResponse` from `backend/app/models/geo.py`.
*/
export interface IpLookupResponse {
/** The queried IP address. */
ip: string;
/** Jails in which the IP is currently banned. */
currently_banned_in: string[];
/** Geo-location data, or `null` when the lookup failed. */
geo: GeoDetail | null;
}