Fix hub AI save: background PM2 restart after JSON response.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-08 23:06:31 +08:00
parent 823aeda42a
commit aaa72c7961
5 changed files with 32 additions and 21 deletions
+9 -4
View File
@@ -195,22 +195,27 @@ def apply_ai_env_to_all(updates: dict[str, str]) -> dict[str, Any]:
}
def restart_hub_and_instances_pm2() -> dict[str, Any]:
def restart_instances_then_hub_pm2() -> dict[str, Any]:
"""先重启三实例,最后重启中控(避免当前请求被中断)。"""
if not sys.platform.startswith("linux"):
return {"ok": False, "msg": "仅 Linux 服务器支持 PM2 重启", "results": []}
from lib.instance.instance_pm2_lib import restart_instance_pm2
apps = ["manual-trading-hub"]
results: list[dict[str, Any]] = []
hub_result = _restart_pm2_app("manual-trading-hub")
results.append({"app": "manual-trading-hub", **hub_result})
for ex in ("okx", "binance", "gate"):
r = restart_instance_pm2(ex)
results.append({"exchange": ex, **r})
hub_result = _restart_pm2_app("manual-trading-hub")
results.append({"app": "manual-trading-hub", **hub_result})
ok = all(r.get("ok") for r in results)
return {"ok": ok, "results": results}
def restart_hub_and_instances_pm2() -> dict[str, Any]:
"""兼容旧调用:与 restart_instances_then_hub_pm2 相同顺序。"""
return restart_instances_then_hub_pm2()
def _restart_pm2_app(app_name: str) -> dict[str, Any]:
try:
proc = subprocess.run(
+6 -12
View File
@@ -110,7 +110,7 @@ from env_load import load_hub_dotenv
load_hub_dotenv()
import httpx
from fastapi import Body, FastAPI, File, Form, HTTPException, Request, UploadFile
from fastapi import BackgroundTasks, Body, FastAPI, File, Form, HTTPException, Request, UploadFile
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
@@ -1706,27 +1706,21 @@ def api_get_ai_env(request: Request):
@app.post("/api/settings/ai-env")
def api_save_ai_env(request: Request, body: HubAiEnvBody):
def api_save_ai_env(request: Request, body: HubAiEnvBody, background_tasks: BackgroundTasks):
_require_hub_logged_in(request)
from hub_env_lib import get_hub_ai_env_payload, restart_all_pm2, save_hub_ai_env
result = save_hub_ai_env(body.values or {})
if not result.get("ok"):
raise HTTPException(status_code=400, detail="; ".join(result.get("errors") or ["保存失败"]))
restart_result = None
if body.restart and result.get("restart_required"):
restart_result = restart_all_pm2()
if not restart_result.get("ok"):
raise HTTPException(
status_code=500,
detail="保存成功但 PM2 重启失败,请手动 restart",
)
payload = get_hub_ai_env_payload()
restart_required = bool(body.restart and result.get("restart_required"))
if restart_required:
background_tasks.add_task(restart_all_pm2)
return {
"ok": True,
"changed": result.get("changed") or {},
"restart_required": bool(result.get("restart_required")),
"restart": restart_result,
"restart_required": restart_required,
"sync_status": payload.get("sync_status"),
}
+1 -1
View File
@@ -41,7 +41,7 @@ def save_hub_ai_env(updates: dict[str, str]) -> dict[str, Any]:
def restart_all_pm2() -> dict[str, Any]:
return restart_hub_and_instances_pm2()
return restart_instances_then_hub_pm2()
def restart_hub_pm2() -> dict[str, Any]:
+15 -3
View File
@@ -4409,6 +4409,16 @@
return values;
}
async function parseApiJson(r) {
const ct = (r.headers.get("content-type") || "").toLowerCase();
if (!ct.includes("application/json")) {
const text = await r.text();
const snippet = (text || "").replace(/\s+/g, " ").trim().slice(0, 120);
throw new Error(snippet ? `服务返回非 JSON${snippet}` : `HTTP ${r.status}`);
}
return r.json();
}
async function saveHubAiEnv() {
const status = document.getElementById("hub-ai-env-save-status");
const setStatus = (msg, err) => {
@@ -4423,15 +4433,17 @@
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ values: collectHubAiEnvValues(), restart: true }),
});
const j = await r.json();
const j = await parseApiJson(r);
if (!r.ok) throw new Error(j.detail || j.msg || "保存失败");
if (!j.changed || !Object.keys(j.changed).length) {
setStatus("未修改(与当前配置相同)");
showToast("未修改");
return;
}
setStatus("已同步三所,服务重启中…");
await waitHubHealth();
if (j.restart_required) {
setStatus("已同步三所,服务重启中…");
await waitHubHealth();
}
setStatus("AI 配置已保存并同步至三所实例");
showToast("AI 配置已保存并同步");
await loadHubAiEnvSettings();
+1 -1
View File
@@ -1192,6 +1192,6 @@
<script src="/assets/time_close_ui.js?v=3"></script>
<script src="/assets/options_expiry_countdown.js?v=1"></script>
<script src="/assets/backup.js?v=1"></script>
<script src="/assets/app.js?v=20260708-hub-tp-profit"></script>
<script src="/assets/app.js?v=20260708-hub-ai-save"></script>
</body>
</html>