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:
@@ -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, """);
|
||||
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, """);
|
||||
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>`;
|
||||
|
||||
Reference in New Issue
Block a user