总览与客户端实例按 sim 在上 live 在下排序,实例表增加模式列。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -302,6 +302,8 @@ def fetch_client_version(inst: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"short_sha": "",
|
"short_sha": "",
|
||||||
"branch": "",
|
"branch": "",
|
||||||
"dirty": False,
|
"dirty": False,
|
||||||
|
"mode": "",
|
||||||
|
"mode_label": "",
|
||||||
"msg": "",
|
"msg": "",
|
||||||
"instance_id": inst.get("id"),
|
"instance_id": inst.get("id"),
|
||||||
"instance_name": inst.get("name"),
|
"instance_name": inst.get("name"),
|
||||||
@@ -320,7 +322,7 @@ def fetch_client_version(inst: dict[str, Any]) -> dict[str, Any]:
|
|||||||
if data is None:
|
if data is None:
|
||||||
out["msg"] = err or f"HTTP {r.status_code}"
|
out["msg"] = err or f"HTTP {r.status_code}"
|
||||||
return out
|
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:
|
if key in data:
|
||||||
out[key] = data.get(key)
|
out[key] = data.get(key)
|
||||||
out["ok"] = bool(data.get("ok")) and r.status_code == 200
|
out["ok"] = bool(data.get("ok")) and r.status_code == 200
|
||||||
|
|||||||
+32
-2
@@ -255,7 +255,19 @@
|
|||||||
status +
|
status +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<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>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
fmtPnlHtml(pnl) +
|
fmtPnlHtml(pnl) +
|
||||||
@@ -472,6 +484,24 @@
|
|||||||
"</tbody></table></div>";
|
"</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() {
|
async function refresh() {
|
||||||
const status = document.getElementById("overview-status");
|
const status = document.getElementById("overview-status");
|
||||||
const grid = document.getElementById("overview-grid");
|
const grid = document.getElementById("overview-grid");
|
||||||
@@ -481,7 +511,7 @@
|
|||||||
const r = await fetch("/api/overview");
|
const r = await fetch("/api/overview");
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
if (!d.ok) throw new Error(d.msg || "失败");
|
if (!d.ok) throw new Error(d.msg || "失败");
|
||||||
const list = d.instances || [];
|
const list = sortInstancesByMode(d.instances || []);
|
||||||
renderOverviewSummary(list);
|
renderOverviewSummary(list);
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
grid.innerHTML =
|
grid.innerHTML =
|
||||||
|
|||||||
@@ -7,6 +7,51 @@
|
|||||||
return branch + short + (v.dirty ? " (dirty)" : "");
|
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) {
|
async function refreshVersion(row) {
|
||||||
const iid = row.getAttribute("data-id");
|
const iid = row.getAttribute("data-id");
|
||||||
const verEl = row.querySelector("[data-inst-version]");
|
const verEl = row.querySelector("[data-inst-version]");
|
||||||
@@ -22,13 +67,16 @@
|
|||||||
if (v && v.online) {
|
if (v && v.online) {
|
||||||
verEl.textContent = fmtVer(v);
|
verEl.textContent = fmtVer(v);
|
||||||
verEl.className = "update-version online";
|
verEl.className = "update-version online";
|
||||||
|
applyModeCell(row, v.mode || "");
|
||||||
} else {
|
} else {
|
||||||
verEl.textContent = (v && v.msg) || "离线";
|
verEl.textContent = (v && v.msg) || "离线";
|
||||||
verEl.className = "update-version offline";
|
verEl.className = "update-version offline";
|
||||||
|
applyModeCell(row, (v && v.mode) || "");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
verEl.textContent = "探测失败";
|
verEl.textContent = "探测失败";
|
||||||
verEl.className = "update-version offline";
|
verEl.className = "update-version offline";
|
||||||
|
applyModeCell(row, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,19 +116,23 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function boot() {
|
async function boot() {
|
||||||
bindTokenToggles(document);
|
bindTokenToggles(document);
|
||||||
bindEditReveal();
|
bindEditReveal();
|
||||||
document.querySelectorAll("tr.inst-row[data-id]").forEach(function (row) {
|
const rows = Array.prototype.slice.call(document.querySelectorAll("tr.inst-row[data-id]"));
|
||||||
refreshVersion(row);
|
await Promise.all(
|
||||||
|
rows.map(function (row) {
|
||||||
const btn = row.querySelector("[data-inst-refresh-ver]");
|
const btn = row.querySelector("[data-inst-refresh-ver]");
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.addEventListener("click", function (e) {
|
btn.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
refreshVersion(row);
|
refreshVersion(row).then(sortInstanceRows);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
return refreshVersion(row);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
sortInstanceRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|||||||
@@ -15,6 +15,6 @@
|
|||||||
<div id="overview-grid"></div>
|
<div id="overview-grid"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='hub.js') }}?v=18"></script>
|
<script src="{{ url_for('static', filename='hub.js') }}?v=19"></script>
|
||||||
<script>HubOverview.boot();</script>
|
<script>HubOverview.boot();</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
<th>Token</th>
|
<th>Token</th>
|
||||||
<th>路径</th>
|
<th>路径</th>
|
||||||
<th>状态</th>
|
<th>状态</th>
|
||||||
|
<th>模式</th>
|
||||||
<th>版本</th>
|
<th>版本</th>
|
||||||
<th>标签</th>
|
<th>标签</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for inst in instances %}
|
{% for inst in instances %}
|
||||||
<tr class="inst-row" data-id="{{ inst.id }}" data-name="{{ inst.name }}">
|
<tr class="inst-row" data-id="{{ inst.id }}" data-name="{{ inst.name }}" data-mode="">
|
||||||
<td class="inst-td-name"><strong>{{ inst.name }}</strong></td>
|
<td class="inst-td-name"><strong>{{ inst.name }}</strong></td>
|
||||||
<td class="inst-td-url muted" title="{{ inst.base_url }}">{{ inst.base_url }}</td>
|
<td class="inst-td-url muted" title="{{ inst.base_url }}">{{ inst.base_url }}</td>
|
||||||
<td class="inst-td-token">
|
<td class="inst-td-token">
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
<span class="inst-pill inst-pill--off">停用</span>
|
<span class="inst-pill inst-pill--off">停用</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td><span class="overview-mode-tag muted" data-inst-mode>—</span></td>
|
||||||
<td><span class="update-version muted" data-inst-version>—</span></td>
|
<td><span class="update-version muted" data-inst-version>—</span></td>
|
||||||
<td class="inst-td-tags muted">{{ inst.tags or '—' }}</td>
|
<td class="inst-td-tags muted">{{ inst.tags or '—' }}</td>
|
||||||
<td class="inst-td-ops">
|
<td class="inst-td-ops">
|
||||||
@@ -115,7 +117,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="inst-edit-row">
|
<tr class="inst-edit-row">
|
||||||
<td colspan="8">
|
<td colspan="9">
|
||||||
<input type="checkbox" id="inst-edit-{{ inst.id }}" class="inst-edit-check" data-reveal-token>
|
<input type="checkbox" id="inst-edit-{{ inst.id }}" class="inst-edit-check" data-reveal-token>
|
||||||
<div class="inst-edit-panel">
|
<div class="inst-edit-panel">
|
||||||
<form method="post" class="settings-form inst-form" autocomplete="off">
|
<form method="post" class="settings-form inst-form" autocomplete="off">
|
||||||
@@ -168,5 +170,5 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='instances_page.js') }}?v=3"></script>
|
<script src="{{ url_for('static', filename='instances_page.js') }}?v=4"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user