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:
@@ -91,12 +91,13 @@ class ActiveBan(BaseModel):
|
||||
|
||||
ip: str = Field(..., description="Banned IP address.")
|
||||
jail: str = Field(..., description="Jail holding the ban.")
|
||||
banned_at: str = Field(..., description="ISO 8601 UTC start of the ban.")
|
||||
banned_at: str | None = Field(default=None, description="ISO 8601 UTC start of the ban.")
|
||||
expires_at: str | None = Field(
|
||||
default=None,
|
||||
description="ISO 8601 UTC expiry, or ``null`` if permanent.",
|
||||
)
|
||||
ban_count: int = Field(..., ge=1, description="Running ban count for this IP.")
|
||||
ban_count: int = Field(default=1, ge=1, description="Running ban count for this IP.")
|
||||
country: str | None = Field(default=None, description="ISO 3166-1 alpha-2 country code.")
|
||||
|
||||
|
||||
class ActiveBanListResponse(BaseModel):
|
||||
|
||||
51
backend/app/models/geo.py
Normal file
51
backend/app/models/geo.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Geo and IP lookup Pydantic models.
|
||||
|
||||
Response models for the ``GET /api/geo/lookup/{ip}`` endpoint.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class GeoDetail(BaseModel):
|
||||
"""Enriched geolocation data for an IP address.
|
||||
|
||||
Populated from the ip-api.com free API.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
country_code: str | None = Field(
|
||||
default=None,
|
||||
description="ISO 3166-1 alpha-2 country code.",
|
||||
)
|
||||
country_name: str | None = Field(
|
||||
default=None,
|
||||
description="Human-readable country name.",
|
||||
)
|
||||
asn: str | None = Field(
|
||||
default=None,
|
||||
description="Autonomous System Number (e.g. ``'AS3320'``).",
|
||||
)
|
||||
org: str | None = Field(
|
||||
default=None,
|
||||
description="Organisation associated with the ASN.",
|
||||
)
|
||||
|
||||
|
||||
class IpLookupResponse(BaseModel):
|
||||
"""Response for ``GET /api/geo/lookup/{ip}``.
|
||||
|
||||
Aggregates current ban status and geographical information for an IP.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
ip: str = Field(..., description="The queried IP address.")
|
||||
currently_banned_in: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="Names of jails where this IP is currently banned.",
|
||||
)
|
||||
geo: GeoDetail | None = Field(
|
||||
default=None,
|
||||
description="Enriched geographical and network information.",
|
||||
)
|
||||
@@ -36,6 +36,7 @@ class Jail(BaseModel):
|
||||
find_time: int = Field(..., description="Time window (seconds) for counting failures.")
|
||||
ban_time: int = Field(..., description="Duration (seconds) of a ban. -1 means permanent.")
|
||||
max_retry: int = Field(..., description="Number of failures before a ban is issued.")
|
||||
actions: list[str] = Field(default_factory=list, description="Names of actions attached to this jail.")
|
||||
status: JailStatus | None = Field(default=None, description="Runtime counters.")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user