Fix hub AI save: background PM2 restart after JSON response.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Vendored
+9
-4
@@ -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"):
|
if not sys.platform.startswith("linux"):
|
||||||
return {"ok": False, "msg": "仅 Linux 服务器支持 PM2 重启", "results": []}
|
return {"ok": False, "msg": "仅 Linux 服务器支持 PM2 重启", "results": []}
|
||||||
from lib.instance.instance_pm2_lib import restart_instance_pm2
|
from lib.instance.instance_pm2_lib import restart_instance_pm2
|
||||||
|
|
||||||
apps = ["manual-trading-hub"]
|
|
||||||
results: list[dict[str, Any]] = []
|
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"):
|
for ex in ("okx", "binance", "gate"):
|
||||||
r = restart_instance_pm2(ex)
|
r = restart_instance_pm2(ex)
|
||||||
results.append({"exchange": ex, **r})
|
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)
|
ok = all(r.get("ok") for r in results)
|
||||||
return {"ok": ok, "results": 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]:
|
def _restart_pm2_app(app_name: str) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ from env_load import load_hub_dotenv
|
|||||||
load_hub_dotenv()
|
load_hub_dotenv()
|
||||||
|
|
||||||
import httpx
|
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.responses import FileResponse, HTMLResponse, JSONResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -1706,27 +1706,21 @@ def api_get_ai_env(request: Request):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/api/settings/ai-env")
|
@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)
|
_require_hub_logged_in(request)
|
||||||
from hub_env_lib import get_hub_ai_env_payload, restart_all_pm2, save_hub_ai_env
|
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 {})
|
result = save_hub_ai_env(body.values or {})
|
||||||
if not result.get("ok"):
|
if not result.get("ok"):
|
||||||
raise HTTPException(status_code=400, detail="; ".join(result.get("errors") or ["保存失败"]))
|
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()
|
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 {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"changed": result.get("changed") or {},
|
"changed": result.get("changed") or {},
|
||||||
"restart_required": bool(result.get("restart_required")),
|
"restart_required": restart_required,
|
||||||
"restart": restart_result,
|
|
||||||
"sync_status": payload.get("sync_status"),
|
"sync_status": payload.get("sync_status"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def save_hub_ai_env(updates: dict[str, str]) -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def restart_all_pm2() -> 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]:
|
def restart_hub_pm2() -> dict[str, Any]:
|
||||||
|
|||||||
@@ -4409,6 +4409,16 @@
|
|||||||
return values;
|
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() {
|
async function saveHubAiEnv() {
|
||||||
const status = document.getElementById("hub-ai-env-save-status");
|
const status = document.getElementById("hub-ai-env-save-status");
|
||||||
const setStatus = (msg, err) => {
|
const setStatus = (msg, err) => {
|
||||||
@@ -4423,15 +4433,17 @@
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ values: collectHubAiEnvValues(), restart: true }),
|
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 (!r.ok) throw new Error(j.detail || j.msg || "保存失败");
|
||||||
if (!j.changed || !Object.keys(j.changed).length) {
|
if (!j.changed || !Object.keys(j.changed).length) {
|
||||||
setStatus("未修改(与当前配置相同)");
|
setStatus("未修改(与当前配置相同)");
|
||||||
showToast("未修改");
|
showToast("未修改");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setStatus("已同步三所,服务重启中…");
|
if (j.restart_required) {
|
||||||
await waitHubHealth();
|
setStatus("已同步三所,服务重启中…");
|
||||||
|
await waitHubHealth();
|
||||||
|
}
|
||||||
setStatus("AI 配置已保存并同步至三所实例");
|
setStatus("AI 配置已保存并同步至三所实例");
|
||||||
showToast("AI 配置已保存并同步");
|
showToast("AI 配置已保存并同步");
|
||||||
await loadHubAiEnvSettings();
|
await loadHubAiEnvSettings();
|
||||||
|
|||||||
@@ -1192,6 +1192,6 @@
|
|||||||
<script src="/assets/time_close_ui.js?v=3"></script>
|
<script src="/assets/time_close_ui.js?v=3"></script>
|
||||||
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/assets/backup.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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user