a
This commit is contained in:
parent
18cfeaf5db
commit
fe855df566
|
|
@ -592,6 +592,20 @@ def _parse_supervisor_status(raw: str) -> Tuple[bool, Optional[int], str]:
|
||||||
return False, None, state
|
return False, None, state
|
||||||
return False, None, "UNKNOWN"
|
return False, None, "UNKNOWN"
|
||||||
|
|
||||||
|
def _list_supervisor_process_names(status_all_raw: str) -> list[str]:
|
||||||
|
names: list[str] = []
|
||||||
|
if not status_all_raw:
|
||||||
|
return names
|
||||||
|
for ln in status_all_raw.splitlines():
|
||||||
|
s = (ln or "").strip()
|
||||||
|
if not s:
|
||||||
|
continue
|
||||||
|
# 每行格式:<name> <STATE> ...
|
||||||
|
name = s.split(None, 1)[0].strip()
|
||||||
|
if name:
|
||||||
|
names.append(name)
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
def _get_program_name() -> str:
|
def _get_program_name() -> str:
|
||||||
# 你给的宝塔配置是 [program:auto_sys]
|
# 你给的宝塔配置是 [program:auto_sys]
|
||||||
|
|
@ -880,6 +894,99 @@ async def trading_restart(_admin: Dict[str, Any] = Depends(require_system_admin)
|
||||||
raise HTTPException(status_code=500, detail=f"supervisorctl restart 失败: {e}")
|
raise HTTPException(status_code=500, detail=f"supervisorctl restart 失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/trading/restart-all")
|
||||||
|
async def trading_restart_all(
|
||||||
|
_admin: Dict[str, Any] = Depends(require_system_admin),
|
||||||
|
prefix: str = "auto_sys_acc",
|
||||||
|
include_default: bool = False,
|
||||||
|
do_update: bool = True,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
一键重启所有账号交易进程(supervisor)。
|
||||||
|
|
||||||
|
- 默认重启所有以 auto_sys_acc 开头的 program(例如 auto_sys_acc1/2/3...)
|
||||||
|
- 可选 include_default=true:同时包含 SUPERVISOR_TRADING_PROGRAM(默认 auto_sys)
|
||||||
|
- 可选 do_update=true:先执行 supervisorctl reread/update 再重启(确保新 ini 生效)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
prefix = (prefix or "auto_sys_acc").strip()
|
||||||
|
if not prefix:
|
||||||
|
prefix = "auto_sys_acc"
|
||||||
|
|
||||||
|
# 先读取全量 status,拿到有哪些进程
|
||||||
|
status_all = _run_supervisorctl(["status"])
|
||||||
|
names = _list_supervisor_process_names(status_all)
|
||||||
|
|
||||||
|
targets: list[str] = []
|
||||||
|
for n in names:
|
||||||
|
if n.startswith(prefix):
|
||||||
|
targets.append(n)
|
||||||
|
|
||||||
|
if include_default:
|
||||||
|
default_prog = _get_program_name()
|
||||||
|
if default_prog and default_prog not in targets and default_prog in names:
|
||||||
|
targets.append(default_prog)
|
||||||
|
|
||||||
|
if not targets:
|
||||||
|
return {
|
||||||
|
"message": "未找到可重启的交易进程",
|
||||||
|
"prefix": prefix,
|
||||||
|
"include_default": include_default,
|
||||||
|
"count": 0,
|
||||||
|
"targets": [],
|
||||||
|
"status_all": status_all,
|
||||||
|
}
|
||||||
|
|
||||||
|
reread_out = ""
|
||||||
|
update_out = ""
|
||||||
|
if do_update:
|
||||||
|
try:
|
||||||
|
reread_out = _run_supervisorctl(["reread"])
|
||||||
|
except Exception as e:
|
||||||
|
reread_out = f"failed: {e}"
|
||||||
|
try:
|
||||||
|
update_out = _run_supervisorctl(["update"])
|
||||||
|
except Exception as e:
|
||||||
|
update_out = f"failed: {e}"
|
||||||
|
|
||||||
|
results: list[Dict[str, Any]] = []
|
||||||
|
ok = 0
|
||||||
|
failed = 0
|
||||||
|
for prog in targets:
|
||||||
|
try:
|
||||||
|
out = _run_supervisorctl(["restart", prog])
|
||||||
|
raw = _run_supervisorctl(["status", prog])
|
||||||
|
running, pid, state = _parse_supervisor_status(raw)
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"program": prog,
|
||||||
|
"ok": True,
|
||||||
|
"output": out,
|
||||||
|
"status": {"running": running, "pid": pid, "state": state, "raw": raw},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ok += 1
|
||||||
|
except Exception as e:
|
||||||
|
failed += 1
|
||||||
|
results.append({"program": prog, "ok": False, "error": str(e)})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "已发起批量重启",
|
||||||
|
"prefix": prefix,
|
||||||
|
"include_default": include_default,
|
||||||
|
"do_update": do_update,
|
||||||
|
"count": len(targets),
|
||||||
|
"ok": ok,
|
||||||
|
"failed": failed,
|
||||||
|
"reread": reread_out,
|
||||||
|
"update": update_out,
|
||||||
|
"targets": targets,
|
||||||
|
"results": results,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"批量重启失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/backend/status")
|
@router.get("/backend/status")
|
||||||
async def backend_status(_admin: Dict[str, Any] = Depends(require_system_admin)) -> Dict[str, Any]:
|
async def backend_status(_admin: Dict[str, Any] = Depends(require_system_admin)) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -379,6 +379,20 @@ const ConfigPanel = ({ currentUser }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRestartAllTrading = async () => {
|
||||||
|
if (!window.confirm('确定要重启【所有账号】的交易进程吗?这会让所有用户的交易服务短暂中断(约 3-10 秒),用于升级代码后统一生效。')) return
|
||||||
|
setSystemBusy(true)
|
||||||
|
setMessage('')
|
||||||
|
try {
|
||||||
|
const res = await api.restartAllTradingSystems({ prefix: 'auto_sys_acc', do_update: true })
|
||||||
|
setMessage(`已发起批量重启:共 ${res.count} 个,成功 ${res.ok},失败 ${res.failed}`)
|
||||||
|
} catch (e) {
|
||||||
|
setMessage('批量重启失败: ' + (e?.message || '未知错误'))
|
||||||
|
} finally {
|
||||||
|
setSystemBusy(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadConfigs()
|
loadConfigs()
|
||||||
checkFeasibility()
|
checkFeasibility()
|
||||||
|
|
@ -972,6 +986,15 @@ const ConfigPanel = ({ currentUser }) => {
|
||||||
>
|
>
|
||||||
重启交易系统
|
重启交易系统
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="system-btn primary"
|
||||||
|
onClick={handleRestartAllTrading}
|
||||||
|
disabled={systemBusy}
|
||||||
|
title="批量重启所有账号交易进程(auto_sys_acc*),用于代码升级后统一生效"
|
||||||
|
>
|
||||||
|
重启所有账号交易
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="system-btn primary"
|
className="system-btn primary"
|
||||||
|
|
|
||||||
|
|
@ -508,6 +508,25 @@ export const api = {
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
restartAllTradingSystems: async (opts = {}) => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
if (opts?.prefix) params.set('prefix', String(opts.prefix))
|
||||||
|
if (opts?.include_default) params.set('include_default', 'true')
|
||||||
|
if (opts?.do_update === false) params.set('do_update', 'false')
|
||||||
|
const url = params.toString()
|
||||||
|
? `${buildUrl('/api/system/trading/restart-all')}?${params.toString()}`
|
||||||
|
: buildUrl('/api/system/trading/restart-all')
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: withAccountHeaders({ 'Content-Type': 'application/json' }),
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({ detail: '批量重启失败' }))
|
||||||
|
throw new Error(error.detail || '批量重启失败')
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
},
|
||||||
|
|
||||||
// 后端控制(uvicorn)
|
// 后端控制(uvicorn)
|
||||||
getBackendStatus: async () => {
|
getBackendStatus: async () => {
|
||||||
const response = await fetch(buildUrl('/api/system/backend/status'), { headers: withAccountHeaders() });
|
const response = await fetch(buildUrl('/api/system/backend/status'), { headers: withAccountHeaders() });
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user