Stage 11: polish, cross-cutting concerns & hardening
- 11.1 MainLayout health indicator: warning MessageBar when fail2ban offline - 11.2 formatDate utility + TimezoneProvider + GET /api/setup/timezone - 11.3 Responsive sidebar: auto-collapse <640px, media query listener - 11.4 PageFeedback (PageLoading/PageError/PageEmpty), BanTable updated - 11.5 prefers-reduced-motion: disable sidebar transition - 11.6 WorldMap ARIA: role/tabIndex/aria-label/onKeyDown for countries - 11.7 Health transition logging (fail2ban_came_online/went_offline) - 11.8 Global handlers: Fail2BanConnectionError/ProtocolError -> 502 - 11.9 379 tests pass, 82% coverage, ruff+mypy+tsc+eslint clean - Timezone endpoint: setup_service.get_timezone, 5 new tests
This commit is contained in:
@@ -35,6 +35,7 @@ from app.config import Settings, get_settings
|
||||
from app.db import init_db
|
||||
from app.routers import auth, bans, blocklist, config, dashboard, geo, health, history, jails, server, setup
|
||||
from app.tasks import blocklist_import, health_check
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError, Fail2BanProtocolError
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ensure the bundled fail2ban package is importable from fail2ban-master/
|
||||
@@ -166,6 +167,56 @@ async def _unhandled_exception_handler(
|
||||
)
|
||||
|
||||
|
||||
async def _fail2ban_connection_handler(
|
||||
request: Request,
|
||||
exc: Fail2BanConnectionError,
|
||||
) -> JSONResponse:
|
||||
"""Return a ``502 Bad Gateway`` response when fail2ban is unreachable.
|
||||
|
||||
Args:
|
||||
request: The incoming FastAPI request.
|
||||
exc: The :class:`~app.utils.fail2ban_client.Fail2BanConnectionError`.
|
||||
|
||||
Returns:
|
||||
A :class:`fastapi.responses.JSONResponse` with status 502.
|
||||
"""
|
||||
log.warning(
|
||||
"fail2ban_connection_error",
|
||||
path=request.url.path,
|
||||
method=request.method,
|
||||
error=str(exc),
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={"detail": f"Cannot reach fail2ban: {exc}"},
|
||||
)
|
||||
|
||||
|
||||
async def _fail2ban_protocol_handler(
|
||||
request: Request,
|
||||
exc: Fail2BanProtocolError,
|
||||
) -> JSONResponse:
|
||||
"""Return a ``502 Bad Gateway`` response for fail2ban protocol errors.
|
||||
|
||||
Args:
|
||||
request: The incoming FastAPI request.
|
||||
exc: The :class:`~app.utils.fail2ban_client.Fail2BanProtocolError`.
|
||||
|
||||
Returns:
|
||||
A :class:`fastapi.responses.JSONResponse` with status 502.
|
||||
"""
|
||||
log.warning(
|
||||
"fail2ban_protocol_error",
|
||||
path=request.url.path,
|
||||
method=request.method,
|
||||
error=str(exc),
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={"detail": f"fail2ban protocol error: {exc}"},
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Setup-redirect middleware
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -269,6 +320,11 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
||||
app.add_middleware(SetupRedirectMiddleware)
|
||||
|
||||
# --- Exception handlers ---
|
||||
# Ordered from most specific to least specific. FastAPI evaluates handlers
|
||||
# in the order they were registered, so fail2ban network errors get a 502
|
||||
# rather than falling through to the generic 500 handler.
|
||||
app.add_exception_handler(Fail2BanConnectionError, _fail2ban_connection_handler) # type: ignore[arg-type]
|
||||
app.add_exception_handler(Fail2BanProtocolError, _fail2ban_protocol_handler) # type: ignore[arg-type]
|
||||
app.add_exception_handler(Exception, _unhandled_exception_handler)
|
||||
|
||||
# --- Routers ---
|
||||
|
||||
Reference in New Issue
Block a user