Refactor response handling and health check endpoints
- Enhance response model with additional fields and validation - Update health and server router implementations - Improve frontend type definitions and API integration - Clean up documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -94,7 +94,7 @@ Note on field naming:
|
||||
- All responses with multiple items include a "total" field.
|
||||
"""
|
||||
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Generic, Literal, TypeVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
@@ -326,3 +326,51 @@ class ErrorResponse(BanGuiBaseModel):
|
||||
default=None,
|
||||
description="Unique ID for correlating this error with request logs on both frontend and backend.",
|
||||
)
|
||||
|
||||
|
||||
class HealthResponse(BanGuiBaseModel):
|
||||
"""Standardized response for the health check endpoint.
|
||||
|
||||
Fields:
|
||||
status: Application health status — 'ok' when healthy, 'unavailable' otherwise.
|
||||
fail2ban: fail2ban daemon status — 'online' or 'offline'.
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Healthy (HTTP 200)
|
||||
{
|
||||
"status": "ok",
|
||||
"fail2ban": "online"
|
||||
}
|
||||
|
||||
# Unhealthy (HTTP 503)
|
||||
{
|
||||
"status": "unavailable",
|
||||
"fail2ban": "offline"
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
status: Literal["ok", "unavailable"] = Field(
|
||||
...,
|
||||
description="Application health status: 'ok' when healthy, 'unavailable' otherwise.",
|
||||
)
|
||||
fail2ban: Literal["online", "offline"] = Field(
|
||||
...,
|
||||
description="fail2ban daemon status: 'online' when reachable, 'offline' otherwise.",
|
||||
)
|
||||
|
||||
|
||||
class FlushLogsResponse(BanGuiBaseModel):
|
||||
"""Standardized response for the flush-logs command endpoint.
|
||||
|
||||
Fields:
|
||||
message: Human-readable result message from fail2ban.
|
||||
|
||||
Example:
|
||||
```python
|
||||
{"message": "Success: fail2ban log files were flushed."}
|
||||
```
|
||||
"""
|
||||
|
||||
message: str = Field(..., description="Human-readable result message from fail2ban.")
|
||||
|
||||
@@ -10,11 +10,12 @@ from fastapi import APIRouter, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.dependencies import ServerStatusDep
|
||||
from app.models.response import HealthResponse
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/api/v1/health", tags=["Health"])
|
||||
|
||||
|
||||
@router.get("", summary="Application health check")
|
||||
@router.get("", summary="Application health check", response_model=HealthResponse)
|
||||
async def health_check(server_status: ServerStatusDep) -> JSONResponse:
|
||||
"""Return application and fail2ban status.
|
||||
|
||||
@@ -27,17 +28,16 @@ async def health_check(server_status: ServerStatusDep) -> JSONResponse:
|
||||
server_status: Injected cached server status snapshot.
|
||||
|
||||
Returns:
|
||||
HTTP 200 with ``{"status": "ok", "fail2ban": "online"}`` if healthy,
|
||||
or HTTP 503 with ``{"status": "unavailable", "fail2ban": "offline"}``
|
||||
if fail2ban is unreachable.
|
||||
HTTP 200 with :class:`~app.models.response.HealthResponse` when healthy,
|
||||
HTTP 503 with :class:`~app.models.response.HealthResponse` when fail2ban is offline.
|
||||
"""
|
||||
if not server_status.online:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
content={"status": "unavailable", "fail2ban": "offline"},
|
||||
content=HealthResponse(status="unavailable", fail2ban="offline").model_dump(),
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_200_OK,
|
||||
content={"status": "ok", "fail2ban": "online"},
|
||||
content=HealthResponse(status="ok", fail2ban="online").model_dump(),
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ from fastapi import APIRouter, Request, status
|
||||
|
||||
from app.dependencies import AuthDep, Fail2BanSocketDep
|
||||
from app.mappers import server_mappers
|
||||
from app.models.response import FlushLogsResponse
|
||||
from app.models.server import ServerSettingsResponse, ServerSettingsUpdate
|
||||
from app.services import server_service
|
||||
|
||||
@@ -86,12 +87,13 @@ async def update_server_settings(
|
||||
"/flush-logs",
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Flush and re-open fail2ban log files",
|
||||
response_model=FlushLogsResponse,
|
||||
)
|
||||
async def flush_logs(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> dict[str, str]:
|
||||
) -> FlushLogsResponse:
|
||||
"""Flush and re-open fail2ban log files.
|
||||
|
||||
Useful after log rotation so the daemon writes to the newly created
|
||||
@@ -102,11 +104,11 @@ async def flush_logs(
|
||||
_auth: Validated session.
|
||||
|
||||
Returns:
|
||||
``{"message": "<response from fail2ban>"}``
|
||||
:class:`~app.models.response.FlushLogsResponse` with the result from fail2ban.
|
||||
|
||||
Raises:
|
||||
HTTPException: 400 when the command is rejected.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
result = await server_service.flush_logs(socket_path)
|
||||
return {"message": result}
|
||||
return FlushLogsResponse(message=result)
|
||||
|
||||
Reference in New Issue
Block a user