feat(hub): fold host status by default, add entrust on grid positions, alert on high CPU/memory

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-13 14:14:48 +08:00
parent a89b446d74
commit 9e395b6732
4 changed files with 149 additions and 29 deletions
+61 -7
View File
@@ -69,6 +69,9 @@
let sseReconnectTimer = null;
let hostStatusTimer = null;
const HOST_STATUS_POLL_MS = 5000;
const HOST_STATUS_OPEN_KEY = "hub-host-status-open";
const HOST_RESOURCE_ALERT_THRESHOLD = 85;
const hostResourceAlertLatch = { cpu: false, mem: false };
function fmtHostBytes(n) {
const v = Number(n);
@@ -109,9 +112,61 @@
if (level === "bad") fillEl.classList.add("bad");
}
function checkHostResourceAlert(cpu, mem) {
const msgs = [];
const cpuP = Number(cpu && cpu.percent);
if (Number.isFinite(cpuP) && cpuP >= HOST_RESOURCE_ALERT_THRESHOLD) {
if (!hostResourceAlertLatch.cpu) {
msgs.push("CPU 使用率 " + cpuP + "%");
hostResourceAlertLatch.cpu = true;
}
} else {
hostResourceAlertLatch.cpu = false;
}
const memP = Number(mem && mem.percent);
if (Number.isFinite(memP) && memP >= HOST_RESOURCE_ALERT_THRESHOLD) {
if (!hostResourceAlertLatch.mem) {
msgs.push("内存使用率 " + memP + "%");
hostResourceAlertLatch.mem = true;
}
} else {
hostResourceAlertLatch.mem = false;
}
if (msgs.length) {
window.alert(
"服务器资源告警\n\n" + msgs.join("\n") + "\n\n请及时关注中控服务器负载。"
);
}
}
function hostStatusSummaryText(data) {
if (!data || !data.ok) return (data && data.msg) || "状态不可用";
const cpu = data.cpu || {};
const mem = data.memory || {};
const disk = data.disk || {};
const parts = [];
const host = String(data.hostname || "").trim();
if (host) parts.push(host);
if (cpu.percent != null) parts.push("CPU " + cpu.percent + "%");
if (mem.percent != null) parts.push("内存 " + mem.percent + "%");
if (disk.percent != null) parts.push("硬盘 " + disk.percent + "%");
return parts.join(" · ") || "—";
}
function initHostStatusPanel() {
const panel = document.getElementById("host-status-panel");
if (!panel) return;
panel.open = loadBoolPref(HOST_STATUS_OPEN_KEY, false);
panel.addEventListener("toggle", function () {
saveBoolPref(HOST_STATUS_OPEN_KEY, !!panel.open);
});
}
function renderHostStatusBar(data) {
const panel = document.getElementById("host-status-panel");
const summaryText = document.getElementById("host-status-summary-text");
const bar = document.getElementById("host-status-bar");
if (!bar) return;
if (!panel || !bar) return;
const dot = document.getElementById("host-status-dot");
const name = document.getElementById("host-status-name");
const uptime = document.getElementById("host-status-uptime");
@@ -124,8 +179,9 @@
const diskSub = document.getElementById("host-disk-sub");
const netUp = document.getElementById("host-net-up");
const netDown = document.getElementById("host-net-down");
panel.classList.remove("hidden");
if (summaryText) summaryText.textContent = hostStatusSummaryText(data);
if (!data || !data.ok) {
bar.classList.remove("hidden");
if (dot) dot.className = "host-status-dot bad";
if (name) {
name.textContent = "服务器";
@@ -143,11 +199,11 @@
if (netDown) netDown.textContent = "↓ —";
return;
}
bar.classList.remove("hidden");
const cpu = data.cpu || {};
const mem = data.memory || {};
const disk = data.disk || {};
const net = data.network || {};
checkHostResourceAlert(cpu, mem);
const levels = [
hostMetricLevel(cpu.percent),
hostMetricLevel(mem.percent),
@@ -203,6 +259,7 @@
function startHostStatusPoll() {
stopHostStatusPoll();
initHostStatusPanel();
void fetchHostStatus();
hostStatusTimer = setInterval(fetchHostStatus, HOST_STATUS_POLL_MS);
}
@@ -2585,7 +2642,6 @@
opts
) {
const options = opts || {};
const compact = !!options.compact;
const symAttr = esc(x.symbol || "").replace(/"/g, "&quot;");
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, "&quot;");
const side = sideAttr || "long";
@@ -2603,9 +2659,7 @@
const mo = monitorOrder || {};
const tcBadge =
!isTrendContext(mo, trendPlan) && mo.time_close_enabled ? timeCloseSymbolBadgeHtml(mo) : "";
const actionCell = compact
? `<button type="button" class="btn-close-pos btn-sm danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button>`
: `<div class="pos-action-group">
const actionCell = `<div class="pos-action-group">
<button type="button" class="btn-place-tpsl btn-sm ghost" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}" data-contracts="${contractsAttr}" data-sl="${slAttr}" data-tp="${tpAttr}">委托</button>
<button type="button" class="btn-close-pos btn-sm danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button>
</div>`;