dashboard-nanobot/backend/api/bot_runtime_router.py

139 lines
5.0 KiB
Python

import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect
from sqlmodel import Session
from core.database import engine, get_session
from core.docker_instance import docker_manager
from core.websocket_manager import manager
from services.bot_runtime_service import (
delete_cron_job as delete_cron_job_service,
ensure_monitor_websocket_access,
get_bot_logs as get_bot_logs_service,
list_cron_jobs as list_cron_jobs_service,
relogin_weixin as relogin_weixin_service,
start_cron_job as start_cron_job_service,
stop_cron_job as stop_cron_job_service,
)
from services.runtime_service import docker_callback
router = APIRouter()
logger = logging.getLogger("dashboard.backend")
@router.get("/api/bots/{bot_id}/logs")
def get_bot_logs(
bot_id: str,
tail: Optional[int] = 300,
offset: int = 0,
limit: Optional[int] = None,
reverse: bool = False,
session: Session = Depends(get_session),
):
try:
return get_bot_logs_service(
session,
bot_id=bot_id,
tail=tail,
offset=offset,
limit=limit,
reverse=reverse,
)
except LookupError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.post("/api/bots/{bot_id}/weixin/relogin")
async def relogin_weixin(bot_id: str, session: Session = Depends(get_session)):
try:
return await relogin_weixin_service(session, bot_id=bot_id)
except LookupError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
except RuntimeError as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.get("/api/bots/{bot_id}/cron/jobs")
def list_cron_jobs(bot_id: str, include_disabled: bool = True, session: Session = Depends(get_session)):
try:
return list_cron_jobs_service(session, bot_id=bot_id, include_disabled=include_disabled)
except LookupError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.post("/api/bots/{bot_id}/cron/jobs/{job_id}/stop")
def stop_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_session)):
try:
return stop_cron_job_service(session, bot_id=bot_id, job_id=job_id)
except LookupError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.post("/api/bots/{bot_id}/cron/jobs/{job_id}/start")
def start_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_session)):
try:
return start_cron_job_service(session, bot_id=bot_id, job_id=job_id)
except LookupError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.delete("/api/bots/{bot_id}/cron/jobs/{job_id}")
def delete_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_session)):
try:
return delete_cron_job_service(session, bot_id=bot_id, job_id=job_id)
except LookupError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.websocket("/ws/monitor/{bot_id}")
async def websocket_endpoint(websocket: WebSocket, bot_id: str):
with Session(engine) as session:
try:
ensure_monitor_websocket_access(session, websocket, bot_id)
except PermissionError:
await websocket.close(code=4401, reason="Bot or panel authentication required")
return
except LookupError:
await websocket.close(code=4404, reason="Bot not found")
return
connected = False
try:
await manager.connect(bot_id, websocket)
connected = True
except Exception as exc:
logger.warning("websocket connect failed bot_id=%s detail=%s", bot_id, exc)
try:
await websocket.close(code=1011, reason="WebSocket accept failed")
except Exception:
pass
return
docker_manager.ensure_monitor(bot_id, docker_callback)
try:
while True:
await websocket.receive_text()
with Session(engine) as session:
try:
ensure_monitor_websocket_access(session, websocket, bot_id)
except PermissionError:
await websocket.close(code=4401, reason="Authentication expired")
return
except LookupError:
await websocket.close(code=4404, reason="Bot not found")
return
except WebSocketDisconnect:
pass
except RuntimeError as exc:
msg = str(exc or "").lower()
if "need to call \"accept\" first" not in msg and "not connected" not in msg:
logger.exception("websocket runtime error bot_id=%s", bot_id)
except Exception:
logger.exception("websocket unexpected error bot_id=%s", bot_id)
finally:
if connected:
manager.disconnect(bot_id, websocket)