681 行单文件拆为(保留 admin_settings 为 include_router 聚合入口): - admin_datasources 数据源列表/连通性测试/KeyRing/ingest status (5 路由) - admin_config settings CRUD + 运行日志 (4 路由) - admin_llm LLM agents/models/ping (3 路由) - admin_quality stats/data-completeness/data-quality (4 路由) 所有路由仍挂 /api/v1/admin 且带 dependencies=[Depends(require_admin)]。 app.py 注册方式不变(仍 import admin_settings.router)。
89 lines
2.8 KiB
Python
89 lines
2.8 KiB
Python
"""后台管理:配置项 CRUD(settings)与运行日志查询。
|
|
|
|
所有接口需管理员鉴权(require_admin)。路由前缀 /api/v1/admin。
|
|
配置项白名单见 src/core/runtime_config.py SETTING_DEFS,之外的 key 一律拒绝。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from pydantic import BaseModel
|
|
|
|
from src.api.deps import require_admin
|
|
from src.core.log_buffer import get_entries
|
|
from src.core.runtime_config import (
|
|
SETTING_DEFS,
|
|
clear_runtime_value,
|
|
get_setting_origin,
|
|
mask_value,
|
|
set_runtime_value,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/v1/admin", tags=["admin"], dependencies=[Depends(require_admin)])
|
|
|
|
|
|
class SettingUpdateIn(BaseModel):
|
|
value: str
|
|
|
|
|
|
@router.get("/settings")
|
|
async def list_settings():
|
|
"""全部可配置项(脱敏),供后台各配置页渲染。"""
|
|
out = []
|
|
for key, defn in SETTING_DEFS.items():
|
|
origin, value = await get_setting_origin(key)
|
|
out.append(
|
|
{
|
|
"key": key,
|
|
"label": defn.label,
|
|
"description": defn.description,
|
|
"sensitive": defn.sensitive,
|
|
"configured": origin != "none",
|
|
"masked": mask_value(value, defn.sensitive),
|
|
"origin": origin,
|
|
}
|
|
)
|
|
return out
|
|
|
|
|
|
@router.get("/logs")
|
|
async def read_logs(
|
|
level: str | None = Query(None, description="最低级别: DEBUG/INFO/WARNING/ERROR"),
|
|
keyword: str | None = Query(None, description="消息或 logger 关键字"),
|
|
limit: int = Query(200, ge=1, le=1000),
|
|
):
|
|
"""查询应用运行日志(内存环形缓冲,最新在前;进程重启后清零)。"""
|
|
entries = get_entries(level, keyword, limit)
|
|
return {"entries": entries, "count": len(entries)}
|
|
|
|
|
|
@router.put("/settings/{key}")
|
|
async def update_setting(key: str, body: SettingUpdateIn):
|
|
"""更新配置项(写入 app_settings 覆盖 .env)。传空值请改用 DELETE。"""
|
|
if key not in SETTING_DEFS:
|
|
raise HTTPException(404, f"不支持的配置项: {key}")
|
|
value = body.value.strip()
|
|
if not value:
|
|
raise HTTPException(400, "值不能为空;如需回落 .env 请调用清除接口")
|
|
await set_runtime_value(key, value)
|
|
defn = SETTING_DEFS[key]
|
|
return {"key": key, "masked": mask_value(value, defn.sensitive), "origin": "db"}
|
|
|
|
|
|
@router.delete("/settings/{key}")
|
|
async def clear_setting(key: str):
|
|
"""清除 DB 覆盖值,回落 .env 默认。"""
|
|
if key not in SETTING_DEFS:
|
|
raise HTTPException(404, f"不支持的配置项: {key}")
|
|
await clear_runtime_value(key)
|
|
origin, value = await get_setting_origin(key)
|
|
defn = SETTING_DEFS[key]
|
|
return {
|
|
"key": key,
|
|
"masked": mask_value(value, defn.sensitive),
|
|
"origin": origin,
|
|
}
|