"""节点管理路由。 提供节点注册、查询、删除和手动调用接口。注册数据进入 SQLite 节点注册表, 实际进程启动与回收仍由 NodeManager 负责。 """ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from app.db import Database from app.node_manager import NodeManager from app.schemas import InvokePayload, NodeCreate from wov_sdk.models import InvokeRequest router = APIRouter(prefix="/api/admin/nodes", tags=["nodes"]) def _get_db() -> Database: """从应用状态延迟获取数据库实例。""" from app.main import app return app.state.db def _get_manager() -> NodeManager: """从应用状态延迟获取节点管理器。""" from app.main import app return app.state.node_manager @router.post("") def register_node(payload: NodeCreate, db: Database = Depends(_get_db)) -> dict: """校验并注册节点,返回注册后的 manifest。""" manifest = payload.to_manifest() try: # 协议级校验保证注册表内数据始终合法。 manifest.validate() except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc db.upsert_node(manifest) return manifest.to_dict() @router.get("") def list_nodes(db: Database = Depends(_get_db)) -> list[dict]: """返回全部已注册节点。""" return [manifest.to_dict() for manifest in db.list_nodes()] @router.get("/{node_id}") def get_node(node_id: str, db: Database = Depends(_get_db)) -> dict: """按 ID 返回节点 manifest。""" manifest = db.get_node(node_id) if manifest is None: raise HTTPException(status_code=404, detail="node not found") return manifest.to_dict() @router.delete("/{node_id}") def delete_node( node_id: str, db: Database = Depends(_get_db), manager: NodeManager = Depends(_get_manager), ) -> dict: """删除节点前先停止其全部运行实例。""" if db.get_node(node_id) is None: raise HTTPException(status_code=404, detail="node not found") # 先回收进程再删注册记录,避免残留孤儿进程。 manager.stop_all_for_node(node_id) db.delete_node(node_id) return {"deleted": node_id} @router.post("/{node_id}/invoke") def invoke_node( node_id: str, payload: InvokePayload, manager: NodeManager = Depends(_get_manager), ) -> dict: """管理后台手动调用节点,产物写入固定输出目录。""" from app.config import STORAGE_DIR # 与管理运行共用目录结构,便于调试产物位置。 output_dir = ( STORAGE_DIR / "runs" / payload.run_id / "steps" / node_id ) request = InvokeRequest( run_id=payload.run_id, node_instance_id="", inputs=payload.inputs, params=payload.params, output_dir=str(output_dir), ) response = manager.invoke(node_id, request) return response.to_dict() @router.get("/{node_id}/instances") def list_node_instances( node_id: str, db: Database = Depends(_get_db), ) -> list[dict]: """返回指定节点的全部实例记录。""" if db.get_node(node_id) is None: raise HTTPException(status_code=404, detail="node not found") return [ instance for instance in db.list_instances() if instance["node_id"] == node_id ]