Files
sentinel/app/api/settings.py

78 lines
2.6 KiB
Python
Raw Permalink Normal View History

from __future__ import annotations
import logging
from typing import Literal
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, Field
from redis.asyncio import Redis
from app.config import RUNTIME_SETTINGS_REDIS_KEY, RuntimeSettings, Settings
from app.core.ip_utils import extract_client_ip
from app.dependencies import get_redis, get_runtime_settings, get_settings, require_admin
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/admin/api/settings", tags=["settings"], dependencies=[Depends(require_admin)])
class SettingsResponse(BaseModel):
alert_webhook_url: str | None = None
alert_threshold_count: int = Field(ge=1)
alert_threshold_seconds: int = Field(ge=1)
archive_days: int = Field(ge=1)
failsafe_mode: Literal["open", "closed"]
class SettingsUpdateRequest(SettingsResponse):
pass
def serialize_runtime_settings(runtime_settings: RuntimeSettings) -> dict[str, str]:
return {
"alert_webhook_url": runtime_settings.alert_webhook_url or "",
"alert_threshold_count": str(runtime_settings.alert_threshold_count),
"alert_threshold_seconds": str(runtime_settings.alert_threshold_seconds),
"archive_days": str(runtime_settings.archive_days),
"failsafe_mode": runtime_settings.failsafe_mode,
}
@router.get("", response_model=SettingsResponse)
async def get_runtime_config(
runtime_settings: RuntimeSettings = Depends(get_runtime_settings),
) -> SettingsResponse:
return SettingsResponse(**runtime_settings.model_dump())
@router.put("", response_model=SettingsResponse)
async def update_runtime_config(
payload: SettingsUpdateRequest,
request: Request,
settings: Settings = Depends(get_settings),
redis: Redis | None = Depends(get_redis),
):
if redis is None:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Settings persistence is unavailable because Redis is offline.",
)
updated = RuntimeSettings(**payload.model_dump())
try:
await redis.hset(RUNTIME_SETTINGS_REDIS_KEY, mapping=serialize_runtime_settings(updated))
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Failed to persist runtime settings.",
) from exc
async with request.app.state.runtime_settings_lock:
request.app.state.runtime_settings = updated
logger.info(
"Runtime settings updated.",
extra={"client_ip": extract_client_ip(request, settings)},
)
return SettingsResponse(**updated.model_dump())