中控总览改为后台常驻并行拉取热缓存,浏览器只读缓存并经 SSE 更新。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-07 14:24:26 +08:00
parent 1735c031ea
commit 0e3fffef44
5 changed files with 327 additions and 36 deletions
+88 -12
View File
@@ -581,7 +581,7 @@
if (dd.submitted_sheets != null) msg += " · " + dd.submitted_sheets + " 张";
if (dd.fully_closed) msg += " · 已全平";
alert(msg);
await refresh();
await requestOverviewRefresh();
} else {
alert((dd && dd.msg) || "平仓失败");
btn.disabled = false;
@@ -759,21 +759,34 @@
return best;
}
async function refresh() {
let overviewVersion = 0;
let overviewLoading = false;
let overviewEventSource = null;
let overviewReconnectTimer = null;
async function refresh(opts) {
opts = opts || {};
const silent = !!opts.silent;
const status = document.getElementById("overview-status");
const grid = document.getElementById("overview-grid");
if (!grid) return;
if (status) status.textContent = "刷新中…";
if (overviewLoading) return;
overviewLoading = true;
if (status && !silent) status.textContent = "读取缓存…";
try {
const r = await fetch("/api/overview");
// 只读中控热缓存;后台常驻并行 pull,不在此请求内打实例
const r = await fetch("/api/overview", { credentials: "same-origin" });
const d = await r.json();
if (!d.ok) throw new Error(d.msg || "失败");
if (!d.ok) throw new Error(d.msg || d.error || "失败");
overviewVersion = Number(d.overview_version) || overviewVersion;
const list = sortInstancesByMode(d.instances || []);
renderOverviewSummary(list);
if (global.HubQuotes && HubQuotes.paint) HubQuotes.paint(list);
if (!list.length) {
grid.innerHTML =
'<p class="muted">暂无启用实例,请先到「实例」添加.</p>';
d.pulling || !d.updated_at
? '<p class="muted">后台首次拉取中…</p>'
: '<p class="muted">暂无启用实例,请先到「实例」添加.</p>';
} else if (isHubPhone()) {
grid.innerHTML =
'<div class="hub-phone-cards">' + list.map(renderPhoneCard).join("") + "</div>";
@@ -790,15 +803,20 @@
}
if (status) {
const s = summarizeOverview(list);
const pulled = latestPulledAt(list);
const pulled = latestPulledAt(list) || d.updated_at || "";
const sec = Number(d.poll_interval_sec) || 10;
const live = overviewEventSource && overviewEventSource.readyState === 1;
status.textContent =
"已更新 · 共 " +
(live ? "SSE 已连接 · " : "") +
"缓存 " +
list.length +
" 台 · 在线 " +
s.all.online +
" · 离线 " +
s.all.offline +
(pulled ? " · 拉取时间 " + pulled : "");
(pulled ? " · " + pulled : "") +
(d.pulling ? " · 后台拉取中" : " · 后台每 " + sec + "s 并行拉") +
(d.error ? " · " + d.error : "");
}
} catch (e) {
if (status) status.textContent = e.message || "加载失败";
@@ -808,9 +826,60 @@
box.innerHTML = "";
}
if (global.HubQuotes && HubQuotes.paint) HubQuotes.paint([]);
} finally {
overviewLoading = false;
}
}
function closeOverviewStream() {
if (overviewEventSource) {
overviewEventSource.close();
overviewEventSource = null;
}
if (overviewReconnectTimer) {
clearTimeout(overviewReconnectTimer);
overviewReconnectTimer = null;
}
}
function connectOverviewStream() {
const grid = document.getElementById("overview-grid");
if (!grid || typeof EventSource === "undefined") return;
closeOverviewStream();
overviewEventSource = new EventSource("/api/overview/stream");
overviewEventSource.addEventListener("overview", function (ev) {
try {
const st = JSON.parse(ev.data || "{}");
const ver = Number(st.overview_version) || 0;
if (ver && ver !== overviewVersion) {
refresh({ silent: true });
} else if (st.pulling) {
const status = document.getElementById("overview-status");
if (status) status.textContent = "后台并行拉取中…";
}
} catch (_) {}
});
overviewEventSource.onerror = function () {
closeOverviewStream();
const status = document.getElementById("overview-status");
if (status) status.textContent = "SSE 断开,8s 后重连…";
overviewReconnectTimer = setTimeout(function () {
connectOverviewStream();
refresh({ silent: true });
}, 8000);
};
}
async function requestOverviewRefresh() {
try {
await fetch("/api/overview/refresh", {
method: "POST",
credentials: "same-origin",
});
} catch (_) {}
refresh({ silent: true });
}
const WEEKDAYS = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
const LUNAR_MONTHS = ["正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"];
const LUNAR_INFO = [
@@ -1001,12 +1070,19 @@
function boot() {
const btn = document.getElementById("btn-refresh");
if (btn) btn.addEventListener("click", refresh);
if (btn) btn.addEventListener("click", function () {
requestOverviewRefresh();
});
tickBeijingClock();
setInterval(tickBeijingClock, 1000);
// 先进缓存秒开,再挂 SSE;不再前端定时现场拉实例
refresh();
setInterval(refresh, 10000);
connectOverviewStream();
}
global.HubOverview = { boot: boot, refresh: refresh };
global.HubOverview = {
boot: boot,
refresh: refresh,
requestRefresh: requestOverviewRefresh,
};
})(window);