78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
|
|
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())
|