总览与客户端实例按 sim 在上 live 在下排序,实例表增加模式列。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+32
-2
@@ -255,7 +255,19 @@
|
||||
status +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(inst.mode || "—") +
|
||||
(function () {
|
||||
const m = String(inst.mode || "").toLowerCase();
|
||||
if (m === "sim" || m === "live") {
|
||||
return (
|
||||
'<span class="overview-mode-tag overview-mode-tag--' +
|
||||
m +
|
||||
'">' +
|
||||
escapeHtml(m) +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
return escapeHtml(inst.mode || "—");
|
||||
})() +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtPnlHtml(pnl) +
|
||||
@@ -472,6 +484,24 @@
|
||||
"</tbody></table></div>";
|
||||
}
|
||||
|
||||
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 =
|
||||
|
||||
+143
-91
@@ -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();
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user