系统更新增加客户端「全部更新」,依次远程更新所有启用实例。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-04 10:19:00 +08:00
parent 50963a25fc
commit 6bc69ab371
2 changed files with 403 additions and 340 deletions
+70 -9
View File
@@ -211,20 +211,25 @@
return v; return v;
} }
async function runClientUpdate(row) { async function runClientUpdate(row, opts) {
const options = opts || {};
const iid = row.getAttribute("data-id"); const iid = row.getAttribute("data-id");
const name = row.getAttribute("data-name") || ("#" + iid); const name = row.getAttribute("data-name") || ("#" + iid);
const btn = row.querySelector("[data-client-update]"); const btn = row.querySelector("[data-client-update]");
const statusEl = row.querySelector("[data-client-status]"); const statusEl = row.querySelector("[data-client-status]");
if (!iid || !btn) return; if (!iid || !btn) return { ok: false, name: name, msg: "无效行" };
if (btn.disabled && !options.forceEnabled) {
return { ok: false, name: name, msg: "未启用或忙碌中" };
}
if ( if (
!options.skipConfirm &&
!window.confirm( !window.confirm(
"确认远程更新「" + "确认远程更新「" +
name + name +
"」?\n\n将执行 git pull + pm2 restart。\n有持仓时服务会短暂中断,请谨慎。" "」?\n\n将执行 git pull + pm2 restart。\n有持仓时服务会短暂中断,请谨慎。"
) )
) { ) {
return; return { ok: false, name: name, msg: "已取消" };
} }
btn.disabled = true; btn.disabled = true;
setStatus(statusEl, "读取当前版本…", "pending"); setStatus(statusEl, "读取当前版本…", "pending");
@@ -238,13 +243,11 @@
}); });
const data = res.data || {}; const data = res.data || {};
if (!data.ok) { if (!data.ok) {
setStatus( const msg =
statusEl, data.msg || (res.status ? "更新失败 HTTP " + res.status : "更新失败");
data.msg || (res.status ? "更新失败 HTTP " + res.status : "更新失败"), setStatus(statusEl, msg, "err");
"err"
);
btn.disabled = false; btn.disabled = false;
return; return { ok: false, name: name, msg: msg };
} }
setStatus(statusEl, "已受理 → 等待上线…", "pending"); setStatus(statusEl, "已受理 → 等待上线…", "pending");
try { try {
@@ -255,11 +258,67 @@
"更新完成" "更新完成"
); );
await refreshClientRow(row); await refreshClientRow(row);
return { ok: true, name: name };
} finally { } finally {
btn.disabled = false; btn.disabled = false;
} }
} }
async function runAllClientUpdates() {
const allBtn = document.getElementById("client-update-all");
const allStatus = document.getElementById("client-update-all-status");
const rows = Array.prototype.slice
.call(document.querySelectorAll(".client-update-row"))
.filter(function (row) {
const btn = row.querySelector("[data-client-update]");
return btn && !btn.disabled;
});
if (!rows.length) {
setStatus(allStatus, "没有可更新的启用实例", "err");
return;
}
if (
!window.confirm(
"确认远程更新全部 " +
rows.length +
" 台启用实例?\n\n将依次执行 git pull + pm2 restart。\n有持仓时服务会短暂中断,请谨慎。"
)
) {
return;
}
if (allBtn) allBtn.disabled = true;
const refreshBtn = document.getElementById("client-update-refresh");
if (refreshBtn) refreshBtn.disabled = true;
let okCount = 0;
let failCount = 0;
try {
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const name = row.getAttribute("data-name") || "#" + (i + 1);
setStatus(
allStatus,
"全部更新中 " + (i + 1) + "/" + rows.length + " · " + name,
"pending"
);
const result = await runClientUpdate(row, { skipConfirm: true });
if (result && result.ok) okCount += 1;
else failCount += 1;
}
if (failCount === 0) {
setStatus(allStatus, "全部更新完成 · " + okCount + " 台", "ok");
} else {
setStatus(
allStatus,
"全部更新结束 · 成功 " + okCount + " · 失败 " + failCount,
"err"
);
}
} finally {
if (allBtn) allBtn.disabled = false;
if (refreshBtn) refreshBtn.disabled = false;
}
}
async function loadClients() { async function loadClients() {
const body = document.getElementById("client-update-body"); const body = document.getElementById("client-update-body");
if (!body) return; if (!body) return;
@@ -328,9 +387,11 @@
const hubBtn = document.getElementById("hub-update-btn"); const hubBtn = document.getElementById("hub-update-btn");
const hubRefresh = document.getElementById("hub-update-refresh"); const hubRefresh = document.getElementById("hub-update-refresh");
const clientsRefresh = document.getElementById("client-update-refresh"); const clientsRefresh = document.getElementById("client-update-refresh");
const clientsAll = document.getElementById("client-update-all");
if (hubBtn) hubBtn.addEventListener("click", runHubUpdate); if (hubBtn) hubBtn.addEventListener("click", runHubUpdate);
if (hubRefresh) hubRefresh.addEventListener("click", refreshHubVersion); if (hubRefresh) hubRefresh.addEventListener("click", refreshHubVersion);
if (clientsRefresh) clientsRefresh.addEventListener("click", loadClients); if (clientsRefresh) clientsRefresh.addEventListener("click", loadClients);
if (clientsAll) clientsAll.addEventListener("click", runAllClientUpdates);
refreshHubVersion(); refreshHubVersion();
loadClients(); loadClients();
} }
+3 -1
View File
@@ -166,7 +166,9 @@
<h3>客户端远程更新</h3> <h3>客户端远程更新</h3>
<p class="muted field-hint">经 Bridge 探测版本并下发更新;客户端需已具备 <code>/api/hub/update</code></p> <p class="muted field-hint">经 Bridge 探测版本并下发更新;客户端需已具备 <code>/api/hub/update</code></p>
<div class="settings-actions"> <div class="settings-actions">
<button type="button" class="settings-primary" id="client-update-all">全部更新</button>
<button type="button" class="btn-secondary" id="client-update-refresh">刷新列表</button> <button type="button" class="btn-secondary" id="client-update-refresh">刷新列表</button>
<span class="update-status muted" id="client-update-all-status"></span>
</div> </div>
</div> </div>
<div class="table-wrap"> <div class="table-wrap">
@@ -183,7 +185,7 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ url_for('static', filename='settings_backup.js') }}?v=2"></script> <script src="{{ url_for('static', filename='settings_backup.js') }}?v=2"></script>
<script src="{{ url_for('static', filename='settings_update.js') }}?v=4"></script> <script src="{{ url_for('static', filename='settings_update.js') }}?v=5"></script>
<script> <script>
HubSettingsBackup.boot(); HubSettingsBackup.boot();
HubSettingsUpdate.boot(); HubSettingsUpdate.boot();