wip: 修复了serve的部分bug
This commit is contained in:
parent
27a71c9e49
commit
dcf53524b2
|
|
@ -10,8 +10,6 @@ services:
|
||||||
POSTGRES_DB: pretor
|
POSTGRES_DB: pretor
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres -d pretor"]
|
test: ["CMD-SHELL", "pg_isready -U postgres -d pretor"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
|
|
@ -34,9 +32,6 @@ services:
|
||||||
- POSTGRES_PORT=5432
|
- POSTGRES_PORT=5432
|
||||||
- POSTGRES_DB=pretor
|
- POSTGRES_DB=pretor
|
||||||
- SECRET_KEY=changethiskey12345
|
- SECRET_KEY=changethiskey12345
|
||||||
volumes:
|
|
||||||
- .:/app
|
|
||||||
- /app/frontend/dist # Prevent local uncompiled frontend from overriding the built one
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|
|
||||||
12
main.py
12
main.py
|
|
@ -10,20 +10,26 @@ from pretor.core.individual.control_node.control_node import ControlNode
|
||||||
from pretor.core.workflow.workflow_runner import WorkflowRunningEngine
|
from pretor.core.workflow.workflow_runner import WorkflowRunningEngine
|
||||||
from pretor.core.api import PretorGateway
|
from pretor.core.api import PretorGateway
|
||||||
from ray import serve
|
from ray import serve
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def start_system():
|
async def start_system():
|
||||||
# 1. 初始化 Ray
|
# 1. 初始化 Ray
|
||||||
|
db_host = os.getenv("POSTGRES_HOST", "db")
|
||||||
env_vars = {
|
env_vars = {
|
||||||
"POSTGRES_USER": "postgres",
|
"POSTGRES_USER": "postgres",
|
||||||
"POSTGRES_PASSWORD": "postgres",
|
"POSTGRES_PASSWORD": "postgres",
|
||||||
"POSTGRES_HOST": "127.0.0.1",
|
"POSTGRES_HOST": db_host,
|
||||||
"POSTGRES_PORT": "5432",
|
"POSTGRES_PORT": "5432",
|
||||||
"POSTGRES_DB": "postgres",
|
"POSTGRES_DB": "postgres",
|
||||||
"SECRET_KEY": "yoursecretkey"
|
"SECRET_KEY": "yoursecretkey"
|
||||||
}
|
}
|
||||||
ray.init(ignore_reinit_error=True, runtime_env={"env_vars": env_vars})
|
|
||||||
|
ray.init(ignore_reinit_error=True,
|
||||||
|
dashboard_host="0.0.0.0",
|
||||||
|
dashboard_port=8265,
|
||||||
|
runtime_env={"env_vars": env_vars})
|
||||||
|
|
||||||
|
|
||||||
# 2. 启动数据库组件
|
# 2. 启动数据库组件
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,11 @@ async def update_cluster_state(websocket: WebSocket):
|
||||||
nodes = ray.nodes()
|
nodes = ray.nodes()
|
||||||
payload = [
|
payload = [
|
||||||
{
|
{
|
||||||
"node_id": node["NodeID"],
|
"node_id": node.get("NodeID"),
|
||||||
"node_name": node["NodeName"],
|
"node_name": node.get("NodeName"),
|
||||||
"alive": node["Alive"],
|
"alive": node.get("Alive"),
|
||||||
"resources": node["Resources"],
|
"resources": node.get("Resources", {}),
|
||||||
"remaining": node["RemainingResources"]
|
"remaining": node.get("RemainingResources", {})
|
||||||
}
|
}
|
||||||
for node in nodes
|
for node in nodes
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -12,104 +12,116 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import Dict
|
|
||||||
from fastapi import FastAPI,WebSocket
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
from fastapi.responses import FileResponse
|
|
||||||
import os
|
import os
|
||||||
|
from typing import Dict
|
||||||
|
from fastapi import FastAPI, WebSocket, Request
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
|
|
||||||
from pretor.api.platform.frontend import client_router
|
from pretor.api.platform.frontend import client_router
|
||||||
from pretor.api.auth import auth_router
|
from pretor.api.auth import auth_router
|
||||||
from pretor.api.provider import provider_router
|
from pretor.api.provider import provider_router
|
||||||
from pretor.api.resource import resource_router
|
from pretor.api.resource import resource_router
|
||||||
from pretor.api.cluster import cluster_router
|
from pretor.api.cluster import cluster_router
|
||||||
from pretor.api.agent import agent_router
|
from pretor.api.agent import agent_router
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from fastapi import Request
|
|
||||||
from pretor.utils.error import (
|
from pretor.utils.error import (
|
||||||
DemandError,
|
DemandError, ModelNotExistError, UserError,
|
||||||
ModelNotExistError,
|
UserNotExistError, UserPasswordError, ProviderError,
|
||||||
UserError,
|
ProviderNotExistError, WorkflowError, WorkflowExit
|
||||||
UserNotExistError,
|
|
||||||
UserPasswordError,
|
|
||||||
ProviderError,
|
|
||||||
ProviderNotExistError,
|
|
||||||
WorkflowError,
|
|
||||||
WorkflowExit
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from ray import serve
|
from ray import serve
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.include_router(client_router) # 客户端路径
|
||||||
|
app.include_router(auth_router) # 用户路径
|
||||||
|
app.include_router(provider_router) # 供应商路径
|
||||||
|
app.include_router(resource_router) # 资源路径
|
||||||
|
app.include_router(cluster_router) # 集群信息路径
|
||||||
|
app.include_router(agent_router) # agent路径
|
||||||
|
|
||||||
|
@app.exception_handler(UserNotExistError)
|
||||||
|
async def user_not_exist_handler(request: Request, exc: UserNotExistError):
|
||||||
|
return JSONResponse(status_code=404, content={"message": "用户不存在"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(UserPasswordError)
|
||||||
|
async def user_password_handler(request: Request, exc: UserPasswordError):
|
||||||
|
return JSONResponse(status_code=401, content={"message": "密码错误"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(UserError)
|
||||||
|
async def user_error_handler(request: Request, exc: UserError):
|
||||||
|
return JSONResponse(status_code=400, content={"message": "用户相关错误"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(ProviderNotExistError)
|
||||||
|
async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError):
|
||||||
|
return JSONResponse(status_code=404, content={"message": "服务提供商不存在"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(ProviderError)
|
||||||
|
async def provider_error_handler(request: Request, exc: ProviderError):
|
||||||
|
return JSONResponse(status_code=400, content={"message": "服务提供商错误"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(ModelNotExistError)
|
||||||
|
async def model_not_exist_handler(request: Request, exc: ModelNotExistError):
|
||||||
|
return JSONResponse(status_code=404, content={"message": "模型不存在"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(DemandError)
|
||||||
|
async def demand_error_handler(request: Request, exc: DemandError):
|
||||||
|
return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(WorkflowExit)
|
||||||
|
async def workflow_exit_handler(request: Request, exc: WorkflowExit):
|
||||||
|
return JSONResponse(status_code=400, content={"message": "工作流已退出"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(WorkflowError)
|
||||||
|
async def workflow_error_handler(request: Request, exc: WorkflowError):
|
||||||
|
return JSONResponse(status_code=500, content={"message": "工作流执行错误"})
|
||||||
|
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
|
frontend_dir = os.path.join(base_dir, "frontend", "dist")
|
||||||
|
|
||||||
|
if os.path.exists(frontend_dir):
|
||||||
|
app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/favicon.svg", include_in_schema=False)
|
||||||
|
async def serve_favicon():
|
||||||
|
return FileResponse(os.path.join(frontend_dir, "favicon.svg"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/icons.svg", include_in_schema=False)
|
||||||
|
async def serve_icons():
|
||||||
|
return FileResponse(os.path.join(frontend_dir, "icons.svg"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/{full_path:path}", include_in_schema=False)
|
||||||
|
async def serve_frontend(full_path: str):
|
||||||
|
# 【重要安全修复】避免拦截不存在的 API 路由。如果是调用了不存在的 /api/ 接口,直接返回 404,不返回前端页面
|
||||||
|
if full_path.startswith("api/"):
|
||||||
|
return JSONResponse(status_code=404, content={"detail": "API endpoint not found"})
|
||||||
|
|
||||||
|
index_path = os.path.join(frontend_dir, "index.html")
|
||||||
|
if os.path.exists(index_path):
|
||||||
|
return FileResponse(index_path)
|
||||||
|
return JSONResponse(status_code=404, content={"detail": "Frontend build not found"})
|
||||||
|
else:
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.getLogger("pretor").warning(f"Frontend dist folder not found at {frontend_dir}. Skipping frontend mount.")
|
||||||
|
|
||||||
|
|
||||||
@serve.deployment
|
@serve.deployment
|
||||||
@serve.ingress(app)
|
@serve.ingress(app)
|
||||||
class PretorGateway:
|
class PretorGateway:
|
||||||
gateway: Dict[str, WebSocket]
|
gateway: Dict[str, WebSocket]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.gateway = {}
|
self.gateway = {}
|
||||||
|
|
||||||
app.include_router(client_router)#客户端路径
|
|
||||||
|
|
||||||
app.include_router(auth_router)#用户路径
|
|
||||||
app.include_router(provider_router)#供应商路径
|
|
||||||
app.include_router(resource_router)#资源路径
|
|
||||||
app.include_router(cluster_router)#集群信息路径
|
|
||||||
app.include_router(agent_router)#agent路径
|
|
||||||
|
|
||||||
@app.exception_handler(UserNotExistError)
|
|
||||||
async def user_not_exist_handler(request: Request, exc: UserNotExistError):
|
|
||||||
return JSONResponse(status_code=404, content={"message": "用户不存在"})
|
|
||||||
|
|
||||||
@app.exception_handler(UserPasswordError)
|
|
||||||
async def user_password_handler(request: Request, exc: UserPasswordError):
|
|
||||||
return JSONResponse(status_code=401, content={"message": "密码错误"})
|
|
||||||
|
|
||||||
@app.exception_handler(UserError)
|
|
||||||
async def user_error_handler(request: Request, exc: UserError):
|
|
||||||
return JSONResponse(status_code=400, content={"message": "用户相关错误"})
|
|
||||||
|
|
||||||
@app.exception_handler(ProviderNotExistError)
|
|
||||||
async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError):
|
|
||||||
return JSONResponse(status_code=404, content={"message": "服务提供商不存在"})
|
|
||||||
|
|
||||||
@app.exception_handler(ProviderError)
|
|
||||||
async def provider_error_handler(request: Request, exc: ProviderError):
|
|
||||||
return JSONResponse(status_code=400, content={"message": "服务提供商错误"})
|
|
||||||
|
|
||||||
@app.exception_handler(ModelNotExistError)
|
|
||||||
async def model_not_exist_handler(request: Request, exc: ModelNotExistError):
|
|
||||||
return JSONResponse(status_code=404, content={"message": "模型不存在"})
|
|
||||||
|
|
||||||
@app.exception_handler(DemandError)
|
|
||||||
async def demand_error_handler(request: Request, exc: DemandError):
|
|
||||||
return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"})
|
|
||||||
|
|
||||||
@app.exception_handler(WorkflowExit)
|
|
||||||
async def workflow_exit_handler(request: Request, exc: WorkflowExit):
|
|
||||||
return JSONResponse(status_code=400, content={"message": "工作流已退出"})
|
|
||||||
|
|
||||||
@app.exception_handler(WorkflowError)
|
|
||||||
async def workflow_error_handler(request: Request, exc: WorkflowError):
|
|
||||||
return JSONResponse(status_code=500, content={"message": "工作流执行错误"})
|
|
||||||
|
|
||||||
frontend_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "frontend", "dist")
|
|
||||||
|
|
||||||
if os.path.exists(frontend_dir):
|
|
||||||
app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets")
|
|
||||||
|
|
||||||
# Serve favicon and other top-level static files if they exist
|
|
||||||
@app.get("/favicon.svg", include_in_schema=False)
|
|
||||||
async def serve_favicon():
|
|
||||||
return FileResponse(os.path.join(frontend_dir, "favicon.svg"))
|
|
||||||
|
|
||||||
@app.get("/icons.svg", include_in_schema=False)
|
|
||||||
async def serve_icons():
|
|
||||||
return FileResponse(os.path.join(frontend_dir, "icons.svg"))
|
|
||||||
|
|
||||||
@app.get("/{full_path:path}", include_in_schema=False)
|
|
||||||
async def serve_frontend(full_path: str):
|
|
||||||
# If a path isn't API or assets, fallback to index.html for React Router / SPA handling
|
|
||||||
# In this specific case, it also fixes any root path reloading issues
|
|
||||||
return FileResponse(os.path.join(frontend_dir, "index.html"))
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue