diff --git a/plugins/sentinel/__init__.py b/plugins/sentinel/__init__.py index 06c860d..0bf043c 100644 --- a/plugins/sentinel/__init__.py +++ b/plugins/sentinel/__init__.py @@ -116,6 +116,7 @@ class Plugin(PluginWebMixin): "system": c.get("system", {}), "platform": c.get("platform", {}), "last_seen": c.get("last_seen", 0), + "latency_ms": c.get("latency_ms", 0), "error": c.get("error", ""), }) return web.json_response({"nodes": result}) @@ -195,12 +196,13 @@ class Plugin(PluginWebMixin): for node in nodes: if not node.get("enabled", True): continue - online, sys_info, err = await self._fetch_system(node) + online, sys_info, err, latency = await self._fetch_system(node) _NODE_CACHE[node.get("id", "")] = { "online": online, "system": sys_info or {}, "platform": (sys_info or {}).get("platform", {}), "last_seen": time.time(), + "latency_ms": latency, "error": err if not online else "", } # 每次轮询都推送 SSE (确保实时性) @@ -217,14 +219,17 @@ class Plugin(PluginWebMixin): def _sync_fetch(): try: + t0 = time.time() headers = {"Authorization": f"Bearer {api_key}"} if api_key else {} req = urllib.request.Request(f"{url}{base}/api/system", headers=headers) with urllib.request.urlopen(req, timeout=timeout) as resp: + elapsed = round((time.time() - t0) * 1000) # ms if resp.status == 200: - return True, _json.loads(resp.read().decode()), "" - return False, None, f"HTTP {resp.status}" + return True, _json.loads(resp.read().decode()), "", elapsed + return False, None, f"HTTP {resp.status}", elapsed except Exception as e: - return False, None, str(e)[:120] + elapsed = round((time.time() - t0) * 1000) + return False, None, str(e)[:120], elapsed return await asyncio.get_event_loop().run_in_executor(None, _sync_fetch) @@ -242,6 +247,7 @@ class Plugin(PluginWebMixin): "system": c.get("system", {}), "platform": c.get("platform", {}), "last_seen": c.get("last_seen", 0), + "latency_ms": c.get("latency_ms", 0), "error": c.get("error", ""), }) return result diff --git a/plugins/sentinel/dashboard.html b/plugins/sentinel/dashboard.html index a1a33e0..3c19db0 100644 --- a/plugins/sentinel/dashboard.html +++ b/plugins/sentinel/dashboard.html @@ -157,12 +157,12 @@ window.addEventListener('resize', function(){ self._resize(); }); } HiChart.prototype._resize = function() { - var rect = this.canvas.parentElement.getBoundingClientRect(); - var w = Math.max(rect.width - 8, 100); - var h = 60; + // 用 canvas 自身的 CSS 渲染尺寸 → 内部分辨率 = CSS尺寸 × DPR (保持高清) + var box = this.canvas.getBoundingClientRect(); + var w = box.width, h = box.height; + if (w <= 0 || h <= 0) { w = 280; h = 60; } // fallback this.canvas.width = w * this._dpr; this.canvas.height = h * this._dpr; - // CSS controls display size via width:100%;height:60px this.ctx.setTransform(this._dpr, 0, 0, this._dpr, 0, 0); this._draw(); }; @@ -294,6 +294,7 @@ (sysInfo.trim() ? '
'+esc(sysInfo)+'
' : '')+ '
'+esc(n.url||'')+'
'+ (n.notes ? '
📝 '+esc(n.notes)+'
' : '')+ + '
⏱ —
'+ ''+ '
'+ '
CPU 0%
'+ @@ -311,7 +312,10 @@ '
'; } else { card.innerHTML = - '
'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'
'+ + '
'+ + '
'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'
'+ + '⏱ —'+ + '
'+ (sysInfo.trim() ? '
'+esc(sysInfo)+'
' : '')+ '
'+esc(n.url||'')+'
'+ (n.notes ? '
📝 '+esc(n.notes)+'
' : '')+ @@ -342,6 +346,17 @@ sysEl.textContent = si2; } else if (sysEl) { sysEl.remove(); } + // 延迟 (横版: col-left底部 / 网格: 右上角) + var latEl = card.querySelector('.node-latency') || card.querySelector('.latency-val'); + if (latEl && n.latency_ms > 0) { + var ms = n.latency_ms; + latEl.textContent = '⏱ ' + (ms < 1000 ? ms + 'ms' : (ms/1000).toFixed(1)+'s'); + latEl.style.color = ms < 100 ? 'var(--success)' : ms < 500 ? 'var(--warning,#e0af68)' : 'var(--error)'; + } else if (latEl && n.online === false) { + latEl.textContent = '⏱ —'; + latEl.style.color = 'var(--text-dim)'; + } + var body = isRow ? card.querySelector('.col-right-stack') : card.querySelector('.metrics-body'); if (!body) return; diff --git a/services/web_panel/routes/status.py b/services/web_panel/routes/status.py index a374ea3..16fd2e5 100644 --- a/services/web_panel/routes/status.py +++ b/services/web_panel/routes/status.py @@ -49,14 +49,14 @@ def setup_routes(app, prefix=''): logger.info(f"📡 系统状态WS端点已注册: {prefix}/api/system/ws (已加认证)") def _read_version(): - """Read version from config file (shared helper)""" - ver = "v0.6.0" + """Read version from version.json (shared helper)""" + ver = "v0.0.0" try: - import yaml, os - config_path = os.path.join(os.path.dirname(__file__), "..", "..", "..", "config", "framework", "base_config.yaml") - with open(config_path) as f: - cfg = yaml.safe_load(f) - ver = cfg.get("framework", {}).get("version", ver) + import json, os + vf = os.path.join(os.path.dirname(__file__), "..", "..", "..", "version.json") + with open(vf) as f: + data = json.load(f) + ver = data.get("framework", {}).get("version", ver) except: pass return ver @@ -193,7 +193,7 @@ async def create_backup(req): if sm: try: init = sm.get_service("init") - ver = init.get_config("base").get("framework", {}).get("version", "0.0.0") + ver = _read_version() except: pass name = f"SenSu_backup_{ver}_{_time.strftime('%Y%m%d_%H%M%S')}.zip" dest = _os.path.join(backup_dir, name)