Merge branch 'dev'
This commit is contained in:
@@ -116,6 +116,7 @@ class Plugin(PluginWebMixin):
|
|||||||
"system": c.get("system", {}),
|
"system": c.get("system", {}),
|
||||||
"platform": c.get("platform", {}),
|
"platform": c.get("platform", {}),
|
||||||
"last_seen": c.get("last_seen", 0),
|
"last_seen": c.get("last_seen", 0),
|
||||||
|
"latency_ms": c.get("latency_ms", 0),
|
||||||
"error": c.get("error", ""),
|
"error": c.get("error", ""),
|
||||||
})
|
})
|
||||||
return web.json_response({"nodes": result})
|
return web.json_response({"nodes": result})
|
||||||
@@ -195,12 +196,13 @@ class Plugin(PluginWebMixin):
|
|||||||
for node in nodes:
|
for node in nodes:
|
||||||
if not node.get("enabled", True):
|
if not node.get("enabled", True):
|
||||||
continue
|
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", "")] = {
|
_NODE_CACHE[node.get("id", "")] = {
|
||||||
"online": online,
|
"online": online,
|
||||||
"system": sys_info or {},
|
"system": sys_info or {},
|
||||||
"platform": (sys_info or {}).get("platform", {}),
|
"platform": (sys_info or {}).get("platform", {}),
|
||||||
"last_seen": time.time(),
|
"last_seen": time.time(),
|
||||||
|
"latency_ms": latency,
|
||||||
"error": err if not online else "",
|
"error": err if not online else "",
|
||||||
}
|
}
|
||||||
# 每次轮询都推送 SSE (确保实时性)
|
# 每次轮询都推送 SSE (确保实时性)
|
||||||
@@ -217,14 +219,17 @@ class Plugin(PluginWebMixin):
|
|||||||
|
|
||||||
def _sync_fetch():
|
def _sync_fetch():
|
||||||
try:
|
try:
|
||||||
|
t0 = time.time()
|
||||||
headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
||||||
req = urllib.request.Request(f"{url}{base}/api/system", headers=headers)
|
req = urllib.request.Request(f"{url}{base}/api/system", headers=headers)
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||||
|
elapsed = round((time.time() - t0) * 1000) # ms
|
||||||
if resp.status == 200:
|
if resp.status == 200:
|
||||||
return True, _json.loads(resp.read().decode()), ""
|
return True, _json.loads(resp.read().decode()), "", elapsed
|
||||||
return False, None, f"HTTP {resp.status}"
|
return False, None, f"HTTP {resp.status}", elapsed
|
||||||
except Exception as e:
|
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)
|
return await asyncio.get_event_loop().run_in_executor(None, _sync_fetch)
|
||||||
|
|
||||||
@@ -242,6 +247,7 @@ class Plugin(PluginWebMixin):
|
|||||||
"system": c.get("system", {}),
|
"system": c.get("system", {}),
|
||||||
"platform": c.get("platform", {}),
|
"platform": c.get("platform", {}),
|
||||||
"last_seen": c.get("last_seen", 0),
|
"last_seen": c.get("last_seen", 0),
|
||||||
|
"latency_ms": c.get("latency_ms", 0),
|
||||||
"error": c.get("error", ""),
|
"error": c.get("error", ""),
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -157,12 +157,12 @@
|
|||||||
window.addEventListener('resize', function(){ self._resize(); });
|
window.addEventListener('resize', function(){ self._resize(); });
|
||||||
}
|
}
|
||||||
HiChart.prototype._resize = function() {
|
HiChart.prototype._resize = function() {
|
||||||
var rect = this.canvas.parentElement.getBoundingClientRect();
|
// 用 canvas 自身的 CSS 渲染尺寸 → 内部分辨率 = CSS尺寸 × DPR (保持高清)
|
||||||
var w = Math.max(rect.width - 8, 100);
|
var box = this.canvas.getBoundingClientRect();
|
||||||
var h = 60;
|
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.width = w * this._dpr;
|
||||||
this.canvas.height = h * 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.ctx.setTransform(this._dpr, 0, 0, this._dpr, 0, 0);
|
||||||
this._draw();
|
this._draw();
|
||||||
};
|
};
|
||||||
@@ -294,6 +294,7 @@
|
|||||||
(sysInfo.trim() ? '<div class="node-sys">'+esc(sysInfo)+'</div>' : '')+
|
(sysInfo.trim() ? '<div class="node-sys">'+esc(sysInfo)+'</div>' : '')+
|
||||||
'<div class="node-url">'+esc(n.url||'')+'</div>'+
|
'<div class="node-url">'+esc(n.url||'')+'</div>'+
|
||||||
(n.notes ? '<div class="node-notes">📝 '+esc(n.notes)+'</div>' : '')+
|
(n.notes ? '<div class="node-notes">📝 '+esc(n.notes)+'</div>' : '')+
|
||||||
|
'<div class="node-latency" style="font-size:.7rem;color:var(--text-dim);margin-top:4px">⏱ —</div>'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="col-right-stack">'+
|
'<div class="col-right-stack">'+
|
||||||
'<div class="mtr" id="mtr-'+id+'-cpu"><div class="mtr-data"><span class="rlabel">CPU</span> <span class="rval cpu-val">0%</span></div><canvas id="'+id+'-cpu" height="60"></canvas></div>'+
|
'<div class="mtr" id="mtr-'+id+'-cpu"><div class="mtr-data"><span class="rlabel">CPU</span> <span class="rval cpu-val">0%</span></div><canvas id="'+id+'-cpu" height="60"></canvas></div>'+
|
||||||
@@ -311,7 +312,10 @@
|
|||||||
'</div>';
|
'</div>';
|
||||||
} else {
|
} else {
|
||||||
card.innerHTML =
|
card.innerHTML =
|
||||||
|
'<div style="display:flex;justify-content:space-between;align-items:flex-start">'+
|
||||||
'<div class="node-name">'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'</div>'+
|
'<div class="node-name">'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'</div>'+
|
||||||
|
'<span class="latency-val" style="font-size:.7rem;color:var(--text-dim);flex-shrink:0">⏱ —</span>'+
|
||||||
|
'</div>'+
|
||||||
(sysInfo.trim() ? '<div class="node-sys">'+esc(sysInfo)+'</div>' : '')+
|
(sysInfo.trim() ? '<div class="node-sys">'+esc(sysInfo)+'</div>' : '')+
|
||||||
'<div class="node-url">'+esc(n.url||'')+'</div>'+
|
'<div class="node-url">'+esc(n.url||'')+'</div>'+
|
||||||
(n.notes ? '<div class="node-notes">📝 '+esc(n.notes)+'</div>' : '')+
|
(n.notes ? '<div class="node-notes">📝 '+esc(n.notes)+'</div>' : '')+
|
||||||
@@ -342,6 +346,17 @@
|
|||||||
sysEl.textContent = si2;
|
sysEl.textContent = si2;
|
||||||
} else if (sysEl) { sysEl.remove(); }
|
} 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');
|
var body = isRow ? card.querySelector('.col-right-stack') : card.querySelector('.metrics-body');
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
|
|
||||||
|
|||||||
@@ -49,14 +49,14 @@ def setup_routes(app, prefix=''):
|
|||||||
logger.info(f"📡 系统状态WS端点已注册: {prefix}/api/system/ws (已加认证)")
|
logger.info(f"📡 系统状态WS端点已注册: {prefix}/api/system/ws (已加认证)")
|
||||||
|
|
||||||
def _read_version():
|
def _read_version():
|
||||||
"""Read version from config file (shared helper)"""
|
"""Read version from version.json (shared helper)"""
|
||||||
ver = "v0.6.0"
|
ver = "v0.0.0"
|
||||||
try:
|
try:
|
||||||
import yaml, os
|
import json, os
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "..", "..", "..", "config", "framework", "base_config.yaml")
|
vf = os.path.join(os.path.dirname(__file__), "..", "..", "..", "version.json")
|
||||||
with open(config_path) as f:
|
with open(vf) as f:
|
||||||
cfg = yaml.safe_load(f)
|
data = json.load(f)
|
||||||
ver = cfg.get("framework", {}).get("version", ver)
|
ver = data.get("framework", {}).get("version", ver)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return ver
|
return ver
|
||||||
@@ -193,7 +193,7 @@ async def create_backup(req):
|
|||||||
if sm:
|
if sm:
|
||||||
try:
|
try:
|
||||||
init = sm.get_service("init")
|
init = sm.get_service("init")
|
||||||
ver = init.get_config("base").get("framework", {}).get("version", "0.0.0")
|
ver = _read_version()
|
||||||
except: pass
|
except: pass
|
||||||
name = f"SenSu_backup_{ver}_{_time.strftime('%Y%m%d_%H%M%S')}.zip"
|
name = f"SenSu_backup_{ver}_{_time.strftime('%Y%m%d_%H%M%S')}.zip"
|
||||||
dest = _os.path.join(backup_dir, name)
|
dest = _os.path.join(backup_dir, name)
|
||||||
|
|||||||
Reference in New Issue
Block a user