|
|
|
@@ -191,21 +191,59 @@
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderMonitorGrid(rows) {
|
|
|
|
|
const box = document.getElementById("monitor-grid");
|
|
|
|
|
if (!box) return;
|
|
|
|
|
if (expandedExchangeId && !rows.some((r) => String(r.id) === String(expandedExchangeId))) {
|
|
|
|
|
function closeExchangeFullscreen() {
|
|
|
|
|
expandedExchangeId = "";
|
|
|
|
|
sessionStorage.removeItem("hub_expanded_ex");
|
|
|
|
|
const fs = document.getElementById("exchange-fullscreen");
|
|
|
|
|
if (fs) {
|
|
|
|
|
fs.classList.add("hidden");
|
|
|
|
|
fs.setAttribute("aria-hidden", "true");
|
|
|
|
|
}
|
|
|
|
|
const visible = expandedExchangeId
|
|
|
|
|
? rows.filter((r) => String(r.id) === String(expandedExchangeId))
|
|
|
|
|
: rows;
|
|
|
|
|
box.classList.toggle("grid-monitor-expanded", !!expandedExchangeId);
|
|
|
|
|
const parts = visible.map((r) => renderMonitorCard(r, !!expandedExchangeId));
|
|
|
|
|
box.innerHTML = parts.join("") || '<div class="err">无已启用账户</div>';
|
|
|
|
|
if (!expandedExchangeId) syncMonitorGridColumns(box, rows.length);
|
|
|
|
|
document.body.classList.remove("hub-fullscreen-open");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openExchangeFullscreen(exId) {
|
|
|
|
|
expandedExchangeId = String(exId);
|
|
|
|
|
sessionStorage.setItem("hub_expanded_ex", expandedExchangeId);
|
|
|
|
|
renderMonitorGrid(lastMonitorRows);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderMonitorGrid(rows) {
|
|
|
|
|
const box = document.getElementById("monitor-grid");
|
|
|
|
|
const fs = document.getElementById("exchange-fullscreen");
|
|
|
|
|
const fsInner = document.getElementById("exchange-fullscreen-inner");
|
|
|
|
|
if (!box) return;
|
|
|
|
|
if (expandedExchangeId && !rows.some((r) => String(r.id) === String(expandedExchangeId))) {
|
|
|
|
|
closeExchangeFullscreen();
|
|
|
|
|
}
|
|
|
|
|
box.innerHTML =
|
|
|
|
|
rows.map((r) => renderMonitorCard(r)).join("") || '<div class="err">无已启用账户</div>';
|
|
|
|
|
syncMonitorGridColumns(box, rows.length);
|
|
|
|
|
bindMonitorInteractions(box);
|
|
|
|
|
|
|
|
|
|
if (expandedExchangeId && fs && fsInner) {
|
|
|
|
|
const row = rows.find((r) => String(r.id) === String(expandedExchangeId));
|
|
|
|
|
if (row) {
|
|
|
|
|
fsInner.innerHTML = renderFullscreenExchange(row);
|
|
|
|
|
fs.classList.remove("hidden");
|
|
|
|
|
fs.setAttribute("aria-hidden", "false");
|
|
|
|
|
document.body.classList.add("hub-fullscreen-open");
|
|
|
|
|
bindMonitorInteractions(fsInner);
|
|
|
|
|
fsInner.querySelectorAll(".btn-expand-back").forEach((btn) => {
|
|
|
|
|
btn.onclick = (ev) => {
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
closeExchangeFullscreen();
|
|
|
|
|
renderMonitorGrid(lastMonitorRows);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
closeExchangeFullscreen();
|
|
|
|
|
}
|
|
|
|
|
} else if (fs) {
|
|
|
|
|
fs.classList.add("hidden");
|
|
|
|
|
fs.setAttribute("aria-hidden", "true");
|
|
|
|
|
document.body.classList.remove("hub-fullscreen-open");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function bindMonitorInteractions(box) {
|
|
|
|
@@ -249,22 +287,11 @@
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
box.querySelectorAll(".btn-expand-back").forEach((btn) => {
|
|
|
|
|
btn.onclick = (ev) => {
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
expandedExchangeId = "";
|
|
|
|
|
sessionStorage.removeItem("hub_expanded_ex");
|
|
|
|
|
renderMonitorGrid(lastMonitorRows);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
box.querySelectorAll(".card-expand-hit").forEach((hit) => {
|
|
|
|
|
hit.onclick = (ev) => {
|
|
|
|
|
if (ev.target.closest("a, button, input, summary, .pos-orders-collapse")) return;
|
|
|
|
|
const id = hit.closest(".card")?.dataset.exId;
|
|
|
|
|
if (!id || expandedExchangeId) return;
|
|
|
|
|
expandedExchangeId = id;
|
|
|
|
|
sessionStorage.setItem("hub_expanded_ex", id);
|
|
|
|
|
renderMonitorGrid(lastMonitorRows);
|
|
|
|
|
box.querySelectorAll(".card-expand-zone").forEach((zone) => {
|
|
|
|
|
zone.onclick = (ev) => {
|
|
|
|
|
if (ev.target.closest("a, button, input, summary, details, .card-actions")) return;
|
|
|
|
|
const id = zone.closest(".card")?.dataset.exId;
|
|
|
|
|
if (id) openExchangeFullscreen(id);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
box.querySelectorAll("details.pos-orders-collapse[data-collapse-key]").forEach((el) => {
|
|
|
|
@@ -461,77 +488,180 @@
|
|
|
|
|
.join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderStrategySection(orders, trends) {
|
|
|
|
|
const parts = [];
|
|
|
|
|
(orders || []).forEach((o) => {
|
|
|
|
|
parts.push(`<div class="hub-mini-card">
|
|
|
|
|
<div class="hub-mini-title">下单监控 #${esc(o.id)} · ${esc(o.symbol || o.exchange_symbol)}</div>
|
|
|
|
|
<div class="hub-mini-line">${esc(o.direction)} · 触发 ${fmt(o.trigger_price, 4)} · SL ${fmt(o.stop_loss, 4)} · TP ${fmt(o.take_profit, 4)}</div>
|
|
|
|
|
</div>`);
|
|
|
|
|
});
|
|
|
|
|
(trends || []).forEach((t) => {
|
|
|
|
|
parts.push(`<div class="hub-mini-card">
|
|
|
|
|
<div class="hub-mini-title">趋势计划 #${esc(t.id)} · ${esc(t.symbol)}</div>
|
|
|
|
|
<div class="hub-mini-line">${esc(t.direction)} · SL ${fmt(t.stop_loss, 4)} · TP ${fmt(t.take_profit, 4)}</div>
|
|
|
|
|
</div>`);
|
|
|
|
|
});
|
|
|
|
|
return parts.join("");
|
|
|
|
|
function renderOrderMonitorSection(orders) {
|
|
|
|
|
if (!orders || !orders.length) return "";
|
|
|
|
|
return orders
|
|
|
|
|
.map(
|
|
|
|
|
(o) => `<div class="hub-mini-card">
|
|
|
|
|
<div class="hub-mini-title">#${esc(o.id)} · ${esc(o.symbol || o.exchange_symbol)} · ${esc(o.direction)}</div>
|
|
|
|
|
<div class="hub-mini-line">触发 ${fmt(o.trigger_price, 4)} · SL ${fmt(o.stop_loss, 4)} · TP ${fmt(o.take_profit, 4)} · ${esc(o.trade_style || o.monitor_type || "下单监控")}</div>
|
|
|
|
|
</div>`
|
|
|
|
|
)
|
|
|
|
|
.join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderCompactBody(row, ag, pos, hm, flaskOk, keys, orders, trends, kmap) {
|
|
|
|
|
function renderTrendSection(trends) {
|
|
|
|
|
if (!trends || !trends.length) return "";
|
|
|
|
|
return trends
|
|
|
|
|
.map(
|
|
|
|
|
(t) => `<div class="hub-mini-card">
|
|
|
|
|
<div class="hub-mini-title">#${esc(t.id)} · ${esc(t.symbol)} · ${esc(t.direction)}</div>
|
|
|
|
|
<div class="hub-mini-line">SL ${fmt(t.stop_loss, 4)} · TP ${fmt(t.take_profit, 4)} · 状态 ${esc(t.status || "active")}</div>
|
|
|
|
|
</div>`
|
|
|
|
|
)
|
|
|
|
|
.join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderRollSection(rolls) {
|
|
|
|
|
if (!rolls || !rolls.length) return "";
|
|
|
|
|
return rolls
|
|
|
|
|
.map(
|
|
|
|
|
(g) => `<div class="hub-mini-card">
|
|
|
|
|
<div class="hub-mini-title">组 #${esc(g.id)} · 监控单 #${esc(g.order_monitor_id || "—")}</div>
|
|
|
|
|
<div class="hub-mini-line">腿数 ${esc(g.leg_count != null ? g.leg_count : "—")} · 止损 ${fmt(g.current_stop_loss, 4)} · ${esc(g.status || "active")}</div>
|
|
|
|
|
</div>`
|
|
|
|
|
)
|
|
|
|
|
.join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderPositionBlock(exchangeId, x) {
|
|
|
|
|
const symAttr = esc(x.symbol || "").replace(/"/g, """);
|
|
|
|
|
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, """);
|
|
|
|
|
const contractsAttr = esc(String(x.contracts != null ? x.contracts : "")).replace(/"/g, """);
|
|
|
|
|
const cond = Array.isArray(x.conditional_orders) ? x.conditional_orders : [];
|
|
|
|
|
const reg = Array.isArray(x.regular_orders) ? x.regular_orders : [];
|
|
|
|
|
const guess = guessTpslFromCondOrders(x.side, cond);
|
|
|
|
|
const slAttr = esc(String(guess.sl)).replace(/"/g, """);
|
|
|
|
|
const tpAttr = esc(String(guess.tp)).replace(/"/g, """);
|
|
|
|
|
return `<div class="pos-block">
|
|
|
|
|
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>${esc(x.symbol)}</td>
|
|
|
|
|
<td>${esc(x.side)}</td>
|
|
|
|
|
<td>${fmt(x.contracts, 4)}</td>
|
|
|
|
|
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 4)}</td>
|
|
|
|
|
<td class="td-actions">
|
|
|
|
|
<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>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody></table>
|
|
|
|
|
${renderOrdersCollapse(exchangeId, x.symbol, cond, reg)}
|
|
|
|
|
</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderGridBody(row, ag, pos, hm, flaskOk, keys, orders, trends, rolls, kmap) {
|
|
|
|
|
let inner = `<div class="stat-row">
|
|
|
|
|
<div class="stat-box"><div class="stat-label">余额</div><div class="stat-value">${fmt(ag.balance_usdt, 2)} <small style="font-size:12px;color:var(--muted)">U</small></div></div>
|
|
|
|
|
<div class="stat-box"><div class="stat-label">浮盈合计</div><div class="stat-value ${pnlCls(ag.total_unrealized_pnl)}">${fmt(ag.total_unrealized_pnl, 4)}</div></div>
|
|
|
|
|
</div>`;
|
|
|
|
|
inner += `<div class="section-title">持仓 · ${pos.length}</div>`;
|
|
|
|
|
inner += `<div class="section-title">交易所持仓</div>`;
|
|
|
|
|
if (pos.length) {
|
|
|
|
|
inner += '<div class="compact-pos-list">';
|
|
|
|
|
pos.forEach((p) => {
|
|
|
|
|
inner += `<div class="compact-pos-line"><span>${esc(p.symbol)} · ${esc(p.side)}</span><span class="${pnlCls(p.unrealized_pnl)}">${fmt(p.unrealized_pnl, 4)}</span></div>`;
|
|
|
|
|
});
|
|
|
|
|
inner += "</div>";
|
|
|
|
|
inner += pos.map((p) => renderPositionBlock(row.id, p)).join("");
|
|
|
|
|
} else {
|
|
|
|
|
inner += '<div class="empty-hint">无持仓</div>';
|
|
|
|
|
}
|
|
|
|
|
const keyN = (row.capabilities || []).includes("key") ? keys.length : 0;
|
|
|
|
|
const stratN = orders.length + ((row.capabilities || []).includes("trend") ? trends.length : 0);
|
|
|
|
|
inner += `<div class="card-expand-hint">点击卡片展开 · 持仓详情 / 关键位 ${keyN} / 策略 ${stratN}</div>`;
|
|
|
|
|
if (orders.length) {
|
|
|
|
|
inner += `<div class="section-title">下单监控 · ${orders.length}</div>`;
|
|
|
|
|
orders.forEach((o) => {
|
|
|
|
|
inner += `<div class="list-line">${esc(o.symbol || o.exchange_symbol)} · ${esc(o.direction)} · 触发 ${fmt(o.trigger_price, 4)}</div>`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if ((row.capabilities || []).includes("key")) {
|
|
|
|
|
inner += `<div class="section-title">关键位</div>`;
|
|
|
|
|
if (!flaskOk) {
|
|
|
|
|
const fe = row.flask_error || hm.msg || hm.error || "策略 Flask 未连通";
|
|
|
|
|
inner += `<div class="err">${esc(fe)}</div>`;
|
|
|
|
|
} else if (!keys.length) {
|
|
|
|
|
inner += '<div class="empty-hint">当前无记录</div>';
|
|
|
|
|
} else {
|
|
|
|
|
keys.forEach((k) => {
|
|
|
|
|
const kp = kmap[k.id] || kmap[String(k.id)] || {};
|
|
|
|
|
const mt = k.monitor_type || k.type || "";
|
|
|
|
|
let line = `${esc(k.symbol)} · ${esc(mt)} · ${k.upper} / ${k.lower}`;
|
|
|
|
|
if (kp.price_display != null || kp.price != null) {
|
|
|
|
|
line += ` · ${esc(kp.price_display != null ? kp.price_display : kp.price)}`;
|
|
|
|
|
}
|
|
|
|
|
line += ` · ${esc(kp.gate_summary || "-")}`;
|
|
|
|
|
inner += `<div class="list-line">${line}</div>`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ((row.capabilities || []).includes("trend") && trends.length) {
|
|
|
|
|
inner += `<div class="section-title">趋势回调 · ${trends.length}</div>`;
|
|
|
|
|
trends.forEach((t) => {
|
|
|
|
|
inner += `<div class="list-line">#${t.id} ${esc(t.symbol)} ${t.direction} · SL ${t.stop_loss} · TP ${t.take_profit}</div>`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (rolls.length) {
|
|
|
|
|
inner += `<div class="section-title">顺势加仓 · ${rolls.length}</div>`;
|
|
|
|
|
rolls.forEach((g) => {
|
|
|
|
|
inner += `<div class="list-line">组 #${g.id} · 监控 #${g.order_monitor_id || "—"} · ${g.leg_count != null ? g.leg_count : "—"} 腿</div>`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
inner += `<div class="card-expand-hint">点击标题栏放大全屏 · 查看持仓卡片 / 关键位 / 策略详情</div>`;
|
|
|
|
|
return inner;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderExpandedBody(row, ag, pos, hm, flaskOk, keys, orders, trends, kmap) {
|
|
|
|
|
let inner = `<div class="stat-row">
|
|
|
|
|
<div class="stat-box"><div class="stat-label">余额</div><div class="stat-value">${fmt(ag.balance_usdt, 2)} <small style="font-size:12px;color:var(--muted)">U</small></div></div>
|
|
|
|
|
function renderFullscreenExchange(row) {
|
|
|
|
|
const ag = row.agent || {};
|
|
|
|
|
const pos = Array.isArray(ag.positions) ? ag.positions : [];
|
|
|
|
|
const hm = row.hub_monitor || {};
|
|
|
|
|
const flaskOk = row.flask_ok !== false && hm.ok !== false;
|
|
|
|
|
const keys = flaskOk ? hm.keys || [] : [];
|
|
|
|
|
const orders = flaskOk ? hm.orders || [] : [];
|
|
|
|
|
const trends = flaskOk ? hm.trends || [] : [];
|
|
|
|
|
const rolls = flaskOk ? hm.rolls || [] : [];
|
|
|
|
|
const kmap = {};
|
|
|
|
|
(row.key_prices || []).forEach((k) => {
|
|
|
|
|
kmap[k.id] = k;
|
|
|
|
|
});
|
|
|
|
|
const flaskOpen = row.flask_url_browser || row.flask_url;
|
|
|
|
|
const strategyUrl = flaskOpen ? esc(flaskOpen.replace(/\/$/, "") + "/strategy") : "";
|
|
|
|
|
let html = `<div class="fs-head">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="fs-title">${esc(row.name)}</h2>
|
|
|
|
|
<div class="fs-sub">${esc(flaskOpen || "")}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fs-head-actions">
|
|
|
|
|
<button type="button" class="ghost btn-expand-back">返回监控</button>
|
|
|
|
|
${flaskOpen ? `<a class="btn-link" href="${esc(flaskOpen)}" target="_blank" rel="noopener">打开实例</a>` : ""}
|
|
|
|
|
${strategyUrl ? `<a class="btn-link" href="${strategyUrl}" target="_blank" rel="noopener">策略交易</a>` : ""}
|
|
|
|
|
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>`;
|
|
|
|
|
if (!row.http_ok || ag.ok === false) {
|
|
|
|
|
html += `<div class="err">${esc(row.error || ag.error || "子代理不可用")}</div>`;
|
|
|
|
|
return html;
|
|
|
|
|
}
|
|
|
|
|
html += `<div class="stat-row">
|
|
|
|
|
<div class="stat-box"><div class="stat-label">余额</div><div class="stat-value">${fmt(ag.balance_usdt, 2)} U</div></div>
|
|
|
|
|
<div class="stat-box"><div class="stat-label">浮盈合计</div><div class="stat-value ${pnlCls(ag.total_unrealized_pnl)}">${fmt(ag.total_unrealized_pnl, 4)}</div></div>
|
|
|
|
|
</div>`;
|
|
|
|
|
inner += '<div class="hub-pos-list">';
|
|
|
|
|
html += '<div class="section-title">持仓(每币种一卡)</div><div class="hub-pos-list">';
|
|
|
|
|
if (pos.length) {
|
|
|
|
|
pos.forEach((p) => {
|
|
|
|
|
const mo = findMonitorOrder(orders, p.symbol, p.side);
|
|
|
|
|
inner += renderLivePositionCard(row.id, p, mo);
|
|
|
|
|
html += renderLivePositionCard(row.id, p, findMonitorOrder(orders, p.symbol, p.side));
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
inner += '<div class="pos-empty">暂无持仓</div>';
|
|
|
|
|
html += '<div class="pos-empty">暂无持仓</div>';
|
|
|
|
|
}
|
|
|
|
|
inner += "</div>";
|
|
|
|
|
html += "</div>";
|
|
|
|
|
if ((row.capabilities || []).includes("key")) {
|
|
|
|
|
if (!flaskOk) {
|
|
|
|
|
const fe = row.flask_error || hm.msg || hm.error || "策略 Flask 未连通";
|
|
|
|
|
inner += renderHubSectionCard("关键位", `<div class="err">${esc(fe)}</div>`, "");
|
|
|
|
|
html += renderHubSectionCard("关键位", `<div class="err">${esc(row.flask_error || hm.error || "Flask 未连通")}</div>`, "");
|
|
|
|
|
} else {
|
|
|
|
|
inner += renderHubSectionCard("关键位", renderKeySection(keys, kmap), "当前无关键位记录");
|
|
|
|
|
html += renderHubSectionCard("关键位", renderKeySection(keys, kmap), "当前无关键位记录");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const showStrategy =
|
|
|
|
|
orders.length || ((row.capabilities || []).includes("trend") && trends.length);
|
|
|
|
|
if (showStrategy) {
|
|
|
|
|
inner += renderHubSectionCard(
|
|
|
|
|
"策略",
|
|
|
|
|
renderStrategySection(orders, (row.capabilities || []).includes("trend") ? trends : []),
|
|
|
|
|
"当前无策略记录"
|
|
|
|
|
);
|
|
|
|
|
html += renderHubSectionCard("下单监控", renderOrderMonitorSection(orders), "暂无运行中的下单监控");
|
|
|
|
|
if ((row.capabilities || []).includes("trend")) {
|
|
|
|
|
html += renderHubSectionCard("趋势回调", renderTrendSection(trends), "暂无运行中的趋势回调计划");
|
|
|
|
|
}
|
|
|
|
|
return inner;
|
|
|
|
|
html += renderHubSectionCard("顺势加仓", renderRollSection(rolls), "暂无运行中的顺势加仓组");
|
|
|
|
|
return html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openTpslModal(exchangeId, symbol, side, contracts, slHint, tpHint) {
|
|
|
|
@@ -627,7 +757,13 @@
|
|
|
|
|
if (cancel) cancel.onclick = closeTpslModal;
|
|
|
|
|
if (submit) submit.onclick = () => submitTpslModal();
|
|
|
|
|
document.addEventListener("keydown", (ev) => {
|
|
|
|
|
if (ev.key === "Escape") closeTpslModal();
|
|
|
|
|
if (ev.key === "Escape") {
|
|
|
|
|
closeTpslModal();
|
|
|
|
|
if (expandedExchangeId) {
|
|
|
|
|
closeExchangeFullscreen();
|
|
|
|
|
renderMonitorGrid(lastMonitorRows);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -672,7 +808,7 @@
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderMonitorCard(row, expanded) {
|
|
|
|
|
function renderMonitorCard(row) {
|
|
|
|
|
const ag = row.agent || {};
|
|
|
|
|
const pos = Array.isArray(ag.positions) ? ag.positions : [];
|
|
|
|
|
const hm = row.hub_monitor || {};
|
|
|
|
@@ -680,6 +816,7 @@
|
|
|
|
|
const keys = flaskOk ? hm.keys || [] : [];
|
|
|
|
|
const orders = flaskOk ? hm.orders || [] : [];
|
|
|
|
|
const trends = flaskOk ? hm.trends || [] : [];
|
|
|
|
|
const rolls = flaskOk ? hm.rolls || [] : [];
|
|
|
|
|
const kmap = {};
|
|
|
|
|
(row.key_prices || []).forEach((k) => {
|
|
|
|
|
kmap[k.id] = k;
|
|
|
|
@@ -692,10 +829,8 @@
|
|
|
|
|
} else if (!agOk) {
|
|
|
|
|
inner = `<div class="err">${esc(agErr || "子代理返回失败")}</div>`;
|
|
|
|
|
inner += `<div class="empty-hint">请检查 PM2 子代理与 <code>${esc(row.agent_url || "")}/status</code></div>`;
|
|
|
|
|
} else if (expanded) {
|
|
|
|
|
inner = renderExpandedBody(row, ag, pos, hm, flaskOk, keys, orders, trends, kmap);
|
|
|
|
|
} else {
|
|
|
|
|
inner = renderCompactBody(row, ag, pos, hm, flaskOk, keys, orders, trends, kmap);
|
|
|
|
|
inner = renderGridBody(row, ag, pos, hm, flaskOk, keys, orders, trends, rolls, kmap);
|
|
|
|
|
}
|
|
|
|
|
const online = row.http_ok && agOk;
|
|
|
|
|
const cardCls = online ? "card-online" : "card-offline";
|
|
|
|
@@ -707,12 +842,8 @@
|
|
|
|
|
const openFlask = flaskOpen
|
|
|
|
|
? `<a class="btn-link" href="${esc(flaskOpen)}" target="_blank" rel="noopener">实例</a>`
|
|
|
|
|
: "";
|
|
|
|
|
const backBtn = expanded
|
|
|
|
|
? `<button type="button" class="ghost btn-expand-back">返回</button>`
|
|
|
|
|
: "";
|
|
|
|
|
const expandHit = !expanded && online ? " card-expand-hit" : "";
|
|
|
|
|
return `<div class="card ${cardCls}${expanded ? " card-expanded" : ""}" data-ex-id="${esc(row.id)}">
|
|
|
|
|
<div class="card-head">
|
|
|
|
|
return `<div class="card ${cardCls}" data-ex-id="${esc(row.id)}">
|
|
|
|
|
<div class="card-head card-expand-zone" title="点击放大全屏">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="card-title-row">
|
|
|
|
|
<span class="status-dot ${dotCls}" title="${online ? "在线" : "离线"}"></span>
|
|
|
|
@@ -721,13 +852,12 @@
|
|
|
|
|
<div class="card-sub">${esc(flaskOpen || "")}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-actions">
|
|
|
|
|
${backBtn}
|
|
|
|
|
${openFlask}
|
|
|
|
|
${review}
|
|
|
|
|
<button type="button" class="danger btn-close-ex" data-id="${esc(row.id)}">全平</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body${expandHit}">${inner}</div>
|
|
|
|
|
<div class="card-body">${inner}</div>
|
|
|
|
|
</div>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|