中控总览改为后台常驻并行拉取热缓存,浏览器只读缓存并经 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
+39 -23
View File
@@ -3,8 +3,6 @@ from __future__ import annotations
import os
import sys
import threading
import time
from functools import wraps
from pathlib import Path
@@ -19,6 +17,7 @@ from flask import (
request,
send_file,
session,
stream_with_context,
url_for,
)
@@ -62,6 +61,7 @@ from lib.instances_lib import (
list_instances,
update_instance,
)
from lib.overview_cache_lib import overview_store
from lib.sso_lib import build_embed_open_url
from lib.stats_agg_lib import build_stats_curve, build_stats_summary, normalize_window
from lib.wechat_lib import (
@@ -486,38 +486,54 @@ def desk():
)
def _on_overview_pulled(rows: list) -> None:
"""常驻 pull 回调:健康巡检 + 余额采样(与浏览器是否打开无关)."""
try:
inspect_overview_rows(rows)
except Exception:
pass
try:
record_cash_samples_from_overview(rows)
except Exception:
pass
@app.route("/api/overview")
@login_required
def api_overview():
rows = pull_all_enabled()
return jsonify({"ok": True, "instances": rows})
"""只读热缓存;不在请求内现场拉实例(后台常驻并行 pull)."""
force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
if force:
overview_store.request_refresh()
return jsonify(overview_store.snapshot_dict())
def _health_pull_interval_sec() -> float:
try:
return max(5.0, float(os.getenv("HUB_PULL_INTERVAL_SEC") or 10))
except (TypeError, ValueError):
return 10.0
@app.route("/api/overview/refresh", methods=["POST"])
@login_required
def api_overview_refresh():
overview_store.request_refresh()
return jsonify({"ok": True, "msg": "已触发后台刷新"})
def _health_watch_loop() -> None:
"""后台巡检:即使无人打开总览也会告警;并采样余额曲线."""
while True:
try:
rows = pull_all_enabled()
inspect_overview_rows(rows)
record_cash_samples_from_overview(rows)
except Exception:
pass
time.sleep(_health_pull_interval_sec())
@app.route("/api/overview/stream")
@login_required
def api_overview_stream():
return Response(
stream_with_context(overview_store.iter_sse()),
mimetype="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)
def start_health_watch() -> None:
t = threading.Thread(target=_health_watch_loop, name="hub-health-watch", daemon=True)
t.start()
def start_overview_cache() -> None:
overview_store.start(pull_all_enabled, on_pull=_on_overview_pulled)
start_health_watch()
start_overview_cache()
try:
start_hub_backup_scheduler(