fix(admin): compact Windows Node capability display
This commit is contained in:
parent
06c47e08cb
commit
28347b940f
|
|
@ -74,8 +74,12 @@ class StaticVendorTests(unittest.TestCase):
|
|||
self.assertNotIn('capabilities: ["origin.plot@v2"]', admin_js)
|
||||
self.assertIn("节点能力由客户端自动发现并上报", html)
|
||||
self.assertIn("runtime.capability_runtime", admin_js)
|
||||
self.assertIn('item.health === "ready"', admin_js)
|
||||
self.assertIn("item.adapter_version", admin_js)
|
||||
self.assertIn("item.software_version", admin_js)
|
||||
self.assertIn("可用能力 / 软件版本", admin_js)
|
||||
self.assertNotIn("自动发现能力", admin_js)
|
||||
self.assertIn(".node-capability { display: flex", html)
|
||||
self.assertIn('runtime.origin ? [["origin.plot@v2", runtime.origin]]', admin_js)
|
||||
self.assertIn("ttl_seconds: 600", admin_js)
|
||||
self.assertIn("navigator.clipboard.writeText(value)", admin_js)
|
||||
|
|
|
|||
|
|
@ -181,7 +181,18 @@
|
|||
.node-status.disabled { color: var(--danger); background: var(--accent-soft); }
|
||||
.node-name { font-weight: 600; }
|
||||
.node-id { color: var(--muted); font: 11px/1.4 var(--mono); }
|
||||
.node-runtime { max-width: 280px; white-space: normal; color: var(--muted); }
|
||||
.node-version { margin-top: 3px; color: var(--muted); font: 11px/1.4 var(--mono); }
|
||||
.node-table { min-width: 820px; }
|
||||
.node-table th:nth-child(1), .node-table td:nth-child(1) { width: 260px; }
|
||||
.node-table th:nth-child(2), .node-table td:nth-child(2) { width: 80px; text-align: left; }
|
||||
.node-table th:nth-child(3), .node-table td:nth-child(3) { text-align: left; }
|
||||
.node-table th:nth-child(4), .node-table td:nth-child(4) { width: 90px; }
|
||||
.node-table th:nth-child(5), .node-table td:nth-child(5) { width: 150px; }
|
||||
.node-capabilities { min-width: 360px; }
|
||||
.node-capability { display: flex; align-items: center; gap: 8px; min-width: max-content; }
|
||||
.node-capability + .node-capability { margin-top: 5px; }
|
||||
.node-capability-version { color: var(--muted); font: 11px/1.4 var(--mono); }
|
||||
.node-empty-capability { color: var(--muted); }
|
||||
.node-actions { text-align: right; }
|
||||
#node-enrollment-modal .card { width: min(520px, calc(100vw - 32px)); margin: 0; padding: 0; }
|
||||
.node-modal-head, .node-modal-actions {
|
||||
|
|
|
|||
|
|
@ -178,52 +178,54 @@ function nodeSoftwareName(capability, item) {
|
|||
return item.software || capability;
|
||||
}
|
||||
|
||||
function nodeAvailableCapabilities(node, runtime) {
|
||||
const reported = new Set(node.capabilities || []);
|
||||
return nodeCapabilityRuntimes(runtime).filter(([capability, item]) =>
|
||||
item.health === "ready" && (!reported.size || reported.has(capability))
|
||||
);
|
||||
}
|
||||
|
||||
function nodeCapabilityVersionHTML(capability, item) {
|
||||
const name = nodeSoftwareName(capability, item);
|
||||
const versions = [
|
||||
item.software_version ? `${name} ${item.software_version}` : name,
|
||||
item.adapter_version ? `Adapter ${item.adapter_version}` : "",
|
||||
].filter(Boolean);
|
||||
return `<div class="node-capability">`
|
||||
+ `<span class="chip ok">${escapeHtml(capability)}</span>`
|
||||
+ `<span class="node-capability-version">${escapeHtml(versions.join(" · "))}</span>`
|
||||
+ `</div>`;
|
||||
}
|
||||
|
||||
function renderWindowsNodes() {
|
||||
const rows = softwareNodes.map(node => {
|
||||
const runtime = node.runtime || {};
|
||||
const capabilityRuntimes = nodeCapabilityRuntimes(runtime);
|
||||
const runtimeParts = [
|
||||
node.os_version || "",
|
||||
runtime.desktop_session === true ? "桌面会话" : "",
|
||||
runtime.available_slots != null ? `可用槽位 ${runtime.available_slots}` : "",
|
||||
...capabilityRuntimes.map(([capability, item]) => {
|
||||
const name = nodeSoftwareName(capability, item);
|
||||
return `${name}${item.health === "ready" ? "可用" : "不可用"}`;
|
||||
}),
|
||||
].filter(Boolean);
|
||||
const versionParts = [
|
||||
node.node_version ? `Node ${node.node_version}` : "",
|
||||
...capabilityRuntimes.flatMap(([capability, item]) => {
|
||||
const name = nodeSoftwareName(capability, item);
|
||||
return [
|
||||
item.adapter_version ? `${name} Adapter ${item.adapter_version}` : "",
|
||||
item.software_version ? `${name} ${item.software_version}` : "",
|
||||
];
|
||||
}),
|
||||
].filter(Boolean);
|
||||
const availableCapabilities = nodeAvailableCapabilities(node, runtime);
|
||||
const capabilityHTML = availableCapabilities
|
||||
.map(([capability, item]) => nodeCapabilityVersionHTML(capability, item))
|
||||
.join("");
|
||||
const lastSeen = node.last_seen_at
|
||||
? `<span title="${escapeHtml(fmtTime(node.last_seen_at))}">${escapeHtml(fmtTimeAgo(node.last_seen_at))}</span>`
|
||||
: "—";
|
||||
const disabled = node.status === "disabled";
|
||||
return `<tr data-node-id="${escapeHtml(node.node_id)}">`
|
||||
+ `<td><div class="node-name">${escapeHtml(node.name || "未命名节点")}</div>`
|
||||
+ `<div class="node-id">${escapeHtml(node.node_id)}</div></td>`
|
||||
+ `<div class="node-id">${escapeHtml(node.node_id)}</div>`
|
||||
+ `<div class="node-version">${escapeHtml(node.node_version ? `Node ${node.node_version}` : "Node 版本未上报")}</div></td>`
|
||||
+ `<td>${nodeStatusHTML(node.status)}</td>`
|
||||
+ `<td class="node-runtime">${escapeHtml(runtimeParts.join(" · ") || "—")}</td>`
|
||||
+ `<td class="node-runtime">${escapeHtml(versionParts.join(" · ") || "—")}</td>`
|
||||
+ `<td>${(node.capabilities || []).map(x => `<span class="chip">${escapeHtml(x)}</span>`).join(" ") || "—"}</td>`
|
||||
+ `<td class="node-capabilities">${capabilityHTML || `<span class="node-empty-capability">暂无可用能力</span>`}</td>`
|
||||
+ `<td>${lastSeen}</td>`
|
||||
+ `<td class="node-actions"><button type="button" data-node-toggle="${disabled ? "enable" : "disable"}" `
|
||||
+ `class="${disabled ? "" : "danger"}">${disabled ? "重新启用" : "禁用"}</button> `
|
||||
+ `<button type="button" data-node-delete class="danger">删除</button></td>`
|
||||
+ `</tr>`;
|
||||
}).join("") || `<tr><td colspan="7" class="empty">尚无已注册的 Windows Node</td></tr>`;
|
||||
}).join("") || `<tr><td colspan="5" class="empty">尚无已注册的 Windows Node</td></tr>`;
|
||||
|
||||
$("s-windows-node").innerHTML = `<div class="card"><div class="card-head">`
|
||||
+ `<div><h2>Windows Node(${softwareNodes.length})</h2><div class="node-help">查看节点状态;禁用会立即断开节点并拒绝后续连接。</div></div>`
|
||||
+ `<button id="node-enrollment-open" class="primary" type="button">生成 Windows Node 注册码</button>`
|
||||
+ `</div><div class="scroll-x"><table><thead><tr><th>节点</th><th>状态</th><th>运行环境</th>`
|
||||
+ `<th>版本</th><th>自动发现能力</th><th>最近心跳</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div></div>`;
|
||||
+ `</div><div class="scroll-x"><table class="node-table"><thead><tr><th>节点 / Node 版本</th><th>状态</th>`
|
||||
+ `<th>可用能力 / 软件版本</th><th>最近心跳</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div></div>`;
|
||||
$("node-enrollment-open").onclick = openNodeEnrollmentModal;
|
||||
$("s-windows-node").querySelectorAll("[data-node-toggle]").forEach(button => {
|
||||
button.onclick = () => toggleSoftwareNode(button);
|
||||
|
|
|
|||
Loading…
Reference in New Issue