from fastapi import APIRouter, Cookie, HTTPException, Request from fastapi.responses import HTMLResponse from app.config.system_config import seed_runtime_system_defaults from app.db import auth_db from app.db import chat_assistant_db from app.db.scheduler_db import ( enqueue_manual_trigger, get_job_config, get_scheduler_overview, list_manual_triggers, set_job_enabled, set_job_interval, ) from app.db.runtime_config_db import delete_config, get_config, list_configs, set_config from app.db.system_logs import get_system_error, get_system_error_stats, list_system_errors from app.web.shared import ( RuntimeConfigRequest, SchedulerIntervalRequest, SchedulerToggleRequest, SchedulerTriggerRequest, login_redirect, require_admin, ) def build_router(templates): router = APIRouter() @router.get("/admin.html", response_class=HTMLResponse) async def admin_page(request: Request, altcoin_session: str = Cookie(default="")): if not auth_db.get_user_by_session_token(altcoin_session): return login_redirect() try: require_admin(altcoin_session) except HTTPException as e: return HTMLResponse(content=f"

需要管理员权限

{e.detail}

返回看板", status_code=e.status_code) return templates.TemplateResponse(request=request, name="admin.html", context={"show_nav": True, "active_nav": "admin"}) @router.get("/api/admin/check") async def api_admin_check(altcoin_session: str = Cookie(default="")): try: user = require_admin(altcoin_session) return {"is_admin": True, "email": user.get("email", "")} except HTTPException: return {"is_admin": False} @router.get("/api/admin/stats") async def api_admin_stats(altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return auth_db.get_admin_stats() @router.get("/api/admin/users") async def api_admin_users(search: str = "", offset: int = 0, limit: int = 50, tab: str = "all", altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return auth_db.get_admin_users(search=search, offset=offset, limit=limit, tab=tab) @router.get("/api/admin/orders") async def api_admin_orders(search: str = "", offset: int = 0, limit: int = 50, status: str = "all", altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return auth_db.get_admin_orders(search=search, offset=offset, limit=limit, status=status) @router.get("/api/admin/system-errors") async def api_admin_system_errors( search: str = "", offset: int = 0, limit: int = 50, level: str = "all", source: str = "all", hours: int = 168, altcoin_session: str = Cookie(default=""), ): require_admin(altcoin_session) return list_system_errors(search=search, offset=offset, limit=limit, level=level, source=source, hours=hours) @router.get("/api/admin/system-errors/stats") async def api_admin_system_error_stats(hours: int = 24, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return get_system_error_stats(hours=hours) @router.get("/api/admin/system-errors/{log_id}") async def api_admin_system_error_detail(log_id: int, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) item = get_system_error(log_id) if not item: raise HTTPException(status_code=404, detail="日志不存在") return item @router.get("/api/admin/chat-logs/overview") async def api_admin_chat_logs_overview(hours: int = 24, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return chat_assistant_db.get_chat_admin_overview(hours=hours) @router.get("/api/admin/chat-logs") async def api_admin_chat_logs( hours: int = 24, intent: str = "all", search: str = "", offset: int = 0, limit: int = 50, altcoin_session: str = Cookie(default=""), ): require_admin(altcoin_session) return chat_assistant_db.list_chat_admin_questions(hours=hours, intent=intent, search=search, offset=offset, limit=limit) @router.get("/api/runtime-config") async def api_runtime_config(kind: str = "all", altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) if kind in ("all", "system"): seed_runtime_system_defaults() if kind == "strategy": return {"items": list_configs("strategy")} if kind == "system": return {"items": list_configs("system")} return {"items": list_configs("strategy") + list_configs("system")} @router.get("/api/runtime-config/{kind}/{config_key:path}") async def api_runtime_config_detail(kind: str, config_key: str, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) if kind not in ("strategy", "system"): raise HTTPException(status_code=400, detail="kind must be strategy or system") return {"kind": kind, "config_key": config_key, "config": get_config(kind, config_key, default={})} @router.put("/api/runtime-config/{kind}/{config_key:path}") async def api_runtime_config_update(kind: str, config_key: str, payload: RuntimeConfigRequest, altcoin_session: str = Cookie(default="")): user = require_admin(altcoin_session) if kind not in ("strategy", "system"): raise HTTPException(status_code=400, detail="kind must be strategy or system") config = set_config(kind, config_key, payload.config, description=payload.description, source="admin_page", updated_by=user.get("email", "")) return {"ok": True, "kind": kind, "config_key": config_key, "config": config} @router.delete("/api/runtime-config/{kind}/{config_key:path}") async def api_runtime_config_delete(kind: str, config_key: str, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) if kind not in ("strategy", "system"): raise HTTPException(status_code=400, detail="kind must be strategy or system") return {"ok": delete_config(kind, config_key), "kind": kind, "config_key": config_key} @router.get("/api/scheduler/jobs") async def api_scheduler_jobs(altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return get_scheduler_overview() @router.post("/api/scheduler/jobs/{job_name}/toggle") async def api_scheduler_toggle(job_name: str, payload: SchedulerToggleRequest, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) if not set_job_enabled(job_name, payload.enabled): raise HTTPException(status_code=404, detail="任务不存在") return {"ok": True, "job_name": job_name, "enabled": payload.enabled} @router.post("/api/scheduler/jobs/{job_name}/interval") async def api_scheduler_interval(job_name: str, payload: SchedulerIntervalRequest, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) if payload.every_seconds < 30: raise HTTPException(status_code=400, detail="周期不能低于 30 秒") if not set_job_interval(job_name, payload.every_seconds): raise HTTPException(status_code=404, detail="任务不存在") return {"ok": True, "job_name": job_name, "every_seconds": payload.every_seconds} @router.post("/api/scheduler/jobs/{job_name}/trigger") async def api_scheduler_trigger(job_name: str, payload: SchedulerTriggerRequest, altcoin_session: str = Cookie(default="")): user = require_admin(altcoin_session) job = get_job_config(job_name) if not job: raise HTTPException(status_code=404, detail="任务不存在") if not job.get("enabled") and not payload.force: raise HTTPException(status_code=409, detail="任务已关闭,需要确认后 force=true 才能单次运行") trigger_id = enqueue_manual_trigger(job_name, force=payload.force, requested_by=user.get("email", "")) return {"ok": True, "job_name": job_name, "trigger_id": trigger_id, "force": payload.force} @router.get("/api/scheduler/triggers") async def api_scheduler_triggers(limit: int = 30, altcoin_session: str = Cookie(default="")): require_admin(altcoin_session) return {"items": list_manual_triggers(limit=limit)} return router