(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(); } })();