From 50963a25fc235b15fdf6c146cb384e725f3e69b4 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 4 Oct 2026 10:17:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=80=BB=E8=A7=88=E4=B8=8E=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E5=AE=9E=E4=BE=8B=E6=8C=89=20sim=20=E5=9C=A8=E4=B8=8A?= =?UTF-8?q?=20live=20=E5=9C=A8=E4=B8=8B=E6=8E=92=E5=BA=8F=EF=BC=8C?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E8=A1=A8=E5=A2=9E=E5=8A=A0=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=88=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- lib/bridge_client_lib.py | 4 +- static/hub.js | 34 +++++- static/instances_page.js | 234 ++++++++++++++++++++++++--------------- templates/dashboard.html | 2 +- templates/instances.html | 8 +- 5 files changed, 184 insertions(+), 98 deletions(-) diff --git a/lib/bridge_client_lib.py b/lib/bridge_client_lib.py index 4285c15..6bdb645 100644 --- a/lib/bridge_client_lib.py +++ b/lib/bridge_client_lib.py @@ -302,6 +302,8 @@ def fetch_client_version(inst: dict[str, Any]) -> dict[str, Any]: "short_sha": "", "branch": "", "dirty": False, + "mode": "", + "mode_label": "", "msg": "", "instance_id": inst.get("id"), "instance_name": inst.get("name"), @@ -320,7 +322,7 @@ def fetch_client_version(inst: dict[str, Any]) -> dict[str, Any]: if data is None: out["msg"] = err or f"HTTP {r.status_code}" return out - for key in ("sha", "short_sha", "branch", "dirty", "msg", "repo"): + for key in ("sha", "short_sha", "branch", "dirty", "msg", "repo", "mode", "mode_label"): if key in data: out[key] = data.get(key) out["ok"] = bool(data.get("ok")) and r.status_code == 200 diff --git a/static/hub.js b/static/hub.js index 0e1642e..5c81684 100644 --- a/static/hub.js +++ b/static/hub.js @@ -255,7 +255,19 @@ status + "" + "" + - escapeHtml(inst.mode || "—") + + (function () { + const m = String(inst.mode || "").toLowerCase(); + if (m === "sim" || m === "live") { + return ( + '' + + escapeHtml(m) + + "" + ); + } + return escapeHtml(inst.mode || "—"); + })() + "" + "" + fmtPnlHtml(pnl) + @@ -472,6 +484,24 @@ ""; } + function modeSortKey(mode) { + const m = String(mode || "").toLowerCase(); + if (m === "sim") return 0; + if (m === "live") return 1; + return 2; + } + + function sortInstancesByMode(list) { + return (list || []).slice().sort(function (a, b) { + const ma = modeSortKey((((a || {}).snapshot || {}).instance || {}).mode); + const mb = modeSortKey((((b || {}).snapshot || {}).instance || {}).mode); + if (ma !== mb) return ma - mb; + const ia = Number((a && a.id) || 0); + const ib = Number((b && b.id) || 0); + return ia - ib; + }); + } + async function refresh() { const status = document.getElementById("overview-status"); const grid = document.getElementById("overview-grid"); @@ -481,7 +511,7 @@ const r = await fetch("/api/overview"); const d = await r.json(); if (!d.ok) throw new Error(d.msg || "失败"); - const list = d.instances || []; + const list = sortInstancesByMode(d.instances || []); renderOverviewSummary(list); if (!list.length) { grid.innerHTML = diff --git a/static/instances_page.js b/static/instances_page.js index 108b2b7..0537e9c 100644 --- a/static/instances_page.js +++ b/static/instances_page.js @@ -1,91 +1,143 @@ -(function () { - function fmtVer(v) { - if (!v || !v.online) return v && v.msg ? String(v.msg) : "离线"; - const short = v.short_sha || (v.sha ? String(v.sha).slice(0, 7) : ""); - const branch = v.branch ? v.branch + " · " : ""; - if (!short) return "未知版本"; - return branch + short + (v.dirty ? " (dirty)" : ""); - } - - async function refreshVersion(row) { - const iid = row.getAttribute("data-id"); - const verEl = row.querySelector("[data-inst-version]"); - if (!iid || !verEl) return; - verEl.textContent = "…"; - verEl.className = "update-version muted"; - try { - const r = await fetch("/api/instances/" + iid + "/version", { - credentials: "same-origin", - headers: { Accept: "application/json" }, - }); - const v = await r.json(); - if (v && v.online) { - verEl.textContent = fmtVer(v); - verEl.className = "update-version online"; - } else { - verEl.textContent = (v && v.msg) || "离线"; - verEl.className = "update-version offline"; - } - } catch (e) { - verEl.textContent = "探测失败"; - verEl.className = "update-version offline"; - } - } - - function setTokenVisible(input, toggleBtn, visible) { - if (!input) return; - input.type = visible ? "text" : "password"; - if (toggleBtn) toggleBtn.textContent = visible ? "隐藏" : "显示"; - } - - function bindTokenToggles(root) { - (root || document).querySelectorAll(".inst-token-toggle").forEach(function (btn) { - if (btn.dataset.bound) return; - btn.dataset.bound = "1"; - btn.addEventListener("click", function () { - const wrap = btn.closest(".inst-token-wrap"); - const input = wrap && wrap.querySelector(".inst-token-input"); - if (!input) return; - setTokenVisible(input, btn, input.type === "password"); - }); - }); - } - - function bindEditReveal() { - document.querySelectorAll(".inst-edit-check[data-reveal-token]").forEach(function (cb) { - if (cb.dataset.bound) return; - cb.dataset.bound = "1"; - const sync = function () { - const panel = cb.nextElementSibling; - if (!panel) return; - const input = panel.querySelector("[data-edit-token]"); - const toggle = panel.querySelector(".inst-token-toggle"); - // 展开编辑时明文显示 Token;收起后恢复打码 - setTokenVisible(input, toggle, !!cb.checked); - }; - cb.addEventListener("change", sync); - sync(); - }); - } - - function boot() { - bindTokenToggles(document); - bindEditReveal(); - document.querySelectorAll("tr.inst-row[data-id]").forEach(function (row) { - refreshVersion(row); - const btn = row.querySelector("[data-inst-refresh-ver]"); - if (btn) { - btn.addEventListener("click", function (e) { - e.preventDefault(); - refreshVersion(row); - }); - } - }); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", boot); - } else { - boot(); - } -})(); +(function () { + function fmtVer(v) { + if (!v || !v.online) return v && v.msg ? String(v.msg) : "离线"; + const short = v.short_sha || (v.sha ? String(v.sha).slice(0, 7) : ""); + const branch = v.branch ? v.branch + " · " : ""; + if (!short) return "未知版本"; + return branch + short + (v.dirty ? " (dirty)" : ""); + } + + function modeSortKey(mode) { + const m = String(mode || "").toLowerCase(); + if (m === "sim") return 0; + if (m === "live") return 1; + return 2; + } + + function applyModeCell(row, mode) { + const modeEl = row.querySelector("[data-inst-mode]"); + const m = String(mode || "").toLowerCase(); + row.setAttribute("data-mode", m); + if (!modeEl) return; + if (m === "sim" || m === "live") { + modeEl.textContent = m; + modeEl.className = "overview-mode-tag overview-mode-tag--" + m; + } else { + modeEl.textContent = "—"; + modeEl.className = "overview-mode-tag muted"; + } + } + + function sortInstanceRows() { + const tbody = document.querySelector(".inst-table tbody"); + if (!tbody) return; + const rows = Array.prototype.slice.call(tbody.querySelectorAll("tr.inst-row[data-id]")); + if (rows.length < 2) return; + const pairs = rows.map(function (row) { + return { row: row, edit: row.nextElementSibling }; + }); + pairs.sort(function (a, b) { + const ka = modeSortKey(a.row.getAttribute("data-mode")); + const kb = modeSortKey(b.row.getAttribute("data-mode")); + if (ka !== kb) return ka - kb; + const ia = Number(a.row.getAttribute("data-id") || 0); + const ib = Number(b.row.getAttribute("data-id") || 0); + return ia - ib; + }); + pairs.forEach(function (p) { + tbody.appendChild(p.row); + if (p.edit && p.edit.classList.contains("inst-edit-row")) { + tbody.appendChild(p.edit); + } + }); + } + + async function refreshVersion(row) { + const iid = row.getAttribute("data-id"); + const verEl = row.querySelector("[data-inst-version]"); + if (!iid || !verEl) return; + verEl.textContent = "…"; + verEl.className = "update-version muted"; + try { + const r = await fetch("/api/instances/" + iid + "/version", { + credentials: "same-origin", + headers: { Accept: "application/json" }, + }); + const v = await r.json(); + if (v && v.online) { + verEl.textContent = fmtVer(v); + verEl.className = "update-version online"; + applyModeCell(row, v.mode || ""); + } else { + verEl.textContent = (v && v.msg) || "离线"; + verEl.className = "update-version offline"; + applyModeCell(row, (v && v.mode) || ""); + } + } catch (e) { + verEl.textContent = "探测失败"; + verEl.className = "update-version offline"; + applyModeCell(row, ""); + } + } + + function setTokenVisible(input, toggleBtn, visible) { + if (!input) return; + input.type = visible ? "text" : "password"; + if (toggleBtn) toggleBtn.textContent = visible ? "隐藏" : "显示"; + } + + function bindTokenToggles(root) { + (root || document).querySelectorAll(".inst-token-toggle").forEach(function (btn) { + if (btn.dataset.bound) return; + btn.dataset.bound = "1"; + btn.addEventListener("click", function () { + const wrap = btn.closest(".inst-token-wrap"); + const input = wrap && wrap.querySelector(".inst-token-input"); + if (!input) return; + setTokenVisible(input, btn, input.type === "password"); + }); + }); + } + + function bindEditReveal() { + document.querySelectorAll(".inst-edit-check[data-reveal-token]").forEach(function (cb) { + if (cb.dataset.bound) return; + cb.dataset.bound = "1"; + const sync = function () { + const panel = cb.nextElementSibling; + if (!panel) return; + const input = panel.querySelector("[data-edit-token]"); + const toggle = panel.querySelector(".inst-token-toggle"); + // 展开编辑时明文显示 Token;收起后恢复打码 + setTokenVisible(input, toggle, !!cb.checked); + }; + cb.addEventListener("change", sync); + sync(); + }); + } + + async function boot() { + bindTokenToggles(document); + bindEditReveal(); + const rows = Array.prototype.slice.call(document.querySelectorAll("tr.inst-row[data-id]")); + await Promise.all( + rows.map(function (row) { + const btn = row.querySelector("[data-inst-refresh-ver]"); + if (btn) { + btn.addEventListener("click", function (e) { + e.preventDefault(); + refreshVersion(row).then(sortInstanceRows); + }); + } + return refreshVersion(row); + }) + ); + sortInstanceRows(); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", boot); + } else { + boot(); + } +})(); diff --git a/templates/dashboard.html b/templates/dashboard.html index d9bb543..b45d6c5 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -15,6 +15,6 @@
{% endblock %} {% block scripts %} - + {% endblock %} diff --git a/templates/instances.html b/templates/instances.html index bb0a406..ef24311 100644 --- a/templates/instances.html +++ b/templates/instances.html @@ -80,6 +80,7 @@ Token 路径 状态 + 模式 版本 标签 操作 @@ -87,7 +88,7 @@ {% for inst in instances %} - + {{ inst.name }} {{ inst.base_url }} @@ -101,6 +102,7 @@ 停用 {% endif %} + — — {{ inst.tags or '—' }} @@ -115,7 +117,7 @@ - +
@@ -168,5 +170,5 @@ {% endblock %} {% block scripts %} - + {% endblock %}