| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- # web/server.py
- import os
- import asyncio
- from pathlib import Path
- from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
- from fastapi.staticfiles import StaticFiles
- from fastapi.responses import FileResponse
- from pydantic import BaseModel
- from core.app_manager import AppManager
- from vs_log_macros import VSC_INFO, VSC_ERROR
- app = FastAPI(title="Visa Plugin Manager")
- # === 1. 路径计算 (关键修改) ===
- # 获取 web/server.py 所在的绝对路径目录
- CURRENT_DIR = Path(__file__).resolve().parent
- # 拼接出 static 目录: .../web/static
- STATIC_DIR = CURRENT_DIR / "static"
- # 拼接出 index.html 路径
- INDEX_FILE = STATIC_DIR / "index.html"
- VSC_INFO("web", f"Static Directory configured at: {STATIC_DIR}")
- VSC_INFO("web", f"Index File expected at: {INDEX_FILE}")
- # 确保目录存在
- if not STATIC_DIR.exists():
- VSC_INFO("web", "Static directory missing, creating...")
- STATIC_DIR.mkdir(parents=True, exist_ok=True)
- # === 2. 挂载静态文件 ===
- # 挂载 /static 路径,用于访问 CSS/JS 等资源 (虽然本例是CDN,但保留以备用)
- app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
- # === 3. 根路径路由 ===
- @app.get("/")
- async def read_index():
- # 显式检查文件是否存在,方便调试
- if not INDEX_FILE.exists():
- return {
- "error": "Index file not found",
- "expected_path": str(INDEX_FILE),
- "tip": "Please ensure 'index.html' exists in the 'web/static' folder."
- }
- return FileResponse(str(INDEX_FILE))
- # === 数据模型 ===
- class GroupControl(BaseModel):
- group_id: str
- class UpgradePluginRequest(BaseModel):
- plugin_name: str
- plugin_bin: str
- class UpdateConfigRequest(BaseModel):
- group_id: str
- new_config_str: str
- # === API 接口 ===
- @app.get("/status")
- def get_status():
- return {"data": AppManager.Instance().get_status()}
- @app.post("/start")
- def start_group(payload: GroupControl):
- if AppManager.Instance().start_group(payload.group_id):
- return {"message": f"Group {payload.group_id} started"}
- raise HTTPException(status_code=400, detail="Failed to start group")
- @app.post("/stop")
- def stop_group(payload: GroupControl):
- if AppManager.Instance().stop_group(payload.group_id):
- return {"message": f"Group {payload.group_id} stopped"}
- raise HTTPException(status_code=400, detail="Group not running or failed to stop")
- @app.post("/restart")
- def restart_group(payload: GroupControl):
- if AppManager.Instance().restart_group(payload.group_id):
- return {"message": f"Group {payload.group_id} restarted"}
- raise HTTPException(status_code=400, detail="Failed to restart")
- @app.post("/group_config")
- def get_group_config(payload: GroupControl):
- config = AppManager.Instance().get_group_config(payload.group_id)
- if config:
- return {"message": f"Group {payload.group_id} restarted", "data": config}
- raise HTTPException(status_code=400, detail="Failed to get group config")
- @app.post("/ota/upgrade_plugin")
- def ota_update(payload: UpgradePluginRequest):
- try:
- restarted = AppManager.Instance().ota_upgrade_plugin(payload.plugin_name)
- return {
- "message": f"Plugin {payload.plugin_name} reloaded",
- "restarted_groups": restarted
- }
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
-
- @app.post("/ota/update_config")
- def ota_update(payload: UpdateConfigRequest):
- try:
- AppManager.Instance().ota_update_plugin_config(payload.group_id, payload.new_config_str)
- return {
- "message": f"Plugin {payload.group_id} config updated"
- }
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
-
- @app.websocket("/ws/logs/{group_id}")
- async def websocket_logs(ws: WebSocket, group_id: str):
- await ws.accept()
- queue = asyncio.Queue()
- loop = asyncio.get_running_loop() # ⭐ 只在这里拿
- def log_callback(msg: str):
- loop.call_soon_threadsafe(queue.put_nowait, msg)
- AppManager.Instance().subscribe_executor_logs(group_id, log_callback)
- try:
- while True:
- msg = await queue.get()
- await ws.send_text(msg)
- finally:
- AppManager.Instance().unsubscribe_executor_logs(group_id, log_callback)
- def run_web_server(host="0.0.0.0", port=8000):
- import uvicorn
- # log_level warning 减少控制台刷屏
- uvicorn.run(app, host=host, port=port, log_level="info")
|