diff --git a/app/api/routers/rbac_operator.py b/app/api/routers/rbac_operator.py index 86ad10c..f621004 100644 --- a/app/api/routers/rbac_operator.py +++ b/app/api/routers/rbac_operator.py @@ -5,7 +5,9 @@ """ from __future__ import annotations -from fastapi import APIRouter, Depends, HTTPException, Request +import json + +from fastapi import APIRouter, Depends, HTTPException, Request, Response from pydantic import BaseModel from ..dependencies import get_db @@ -212,6 +214,32 @@ async def compute_provision( ) +@router.api_route( + "/compute/proxy/{path:path}", + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], + response_class=Response, + summary="算力中心管理 API 透传(对齐 compute-engine /api/*)", +) +async def compute_proxy(path: str, request: Request, _u: dict = Depends(require_roles("operator"))): + """算力中心管理转发:把 admin 端请求 1:1 透传到 compute-engine(new-api) 管理 API ``/api/{path}``。 + + 打通全部管理面(models / channels / groups / tokens / users / logs / redemption / ratio / + system-settings 等),保留引擎原始状态码与响应体,使 admin 端可作为 new-api 唯一管理 UI。 + """ + body = await request.body() + json_body = None + if body and request.headers.get("content-type", "").startswith("application/json"): + try: + json_body = json.loads(body) + except Exception: # noqa: BLE001 + json_body = None + params = dict(request.query_params) + result = await compute_client.proxy( + request.method, "/" + path, json_body=json_body, params=params, + ) + return Response(content=result["body"], status_code=int(result["status"]), media_type="application/json") + + # ── 培训业务 · 课程(桥接 app.training courses 表)───────────────────────── @router.get("/courses", summary="课程列表") async def list_courses( diff --git a/app/services/compute_client.py b/app/services/compute_client.py index bde05f1..e52cabe 100644 --- a/app/services/compute_client.py +++ b/app/services/compute_client.py @@ -104,3 +104,21 @@ async def get_logs(user_pat: str) -> dict: """用户 token 流水。""" headers = {"Authorization": f"Bearer {user_pat}"} return await _request(method="GET", path="/api/log/self", headers=headers) + + +async def proxy(method: str, path: str, *, json_body: dict | None = None, params: dict | None = None) -> dict: + """管理 API 透传:转发到引擎 ``/api``,返回 ``{status, body}``。 + + 供 /admin/compute/proxy/** 使用,让 admin 端可 1:1 驱动 compute-engine(new-api) 的完整管理面 + (models/channels/groups/tokens/users/logs/redemption/ratio 等),不改写每个端点契约。 + 保留引擎原始状态码与响应体(网关层不吞错误)。 + """ + url = f"{_base()}/api{path}" + try: + async with httpx.AsyncClient(timeout=config.COMPUTE_TIMEOUT) as client: + resp = await client.request( + method, url, json=json_body, params=params, headers=_admin_headers(), + ) + return {"status": resp.status_code, "body": resp.text or "{}"} + except httpx.HTTPError as exc: + return {"status": 502, "body": '{"error": "%s"}' % str(exc)}