feat: 横版col-extra — 负载/SWAP/磁盘/温度/流量/进程 6项指标
SystemInfoCollector 新增: - swap: SWAP用量 (psutil + /proc/meminfo fallback) - disk: 磁盘用量 - temperature: CPU温度 (psutil + /sys/class/thermal fallback) Sentinel col-extra 显示: 负载 | SWAP | 磁盘 | 温度 | 累计流量 | 进程RSS Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,10 @@
|
||||
#sentinel-grid.layout-row .node-card { display:grid;grid-template-columns:160px 1fr 1fr;gap:12px 16px;align-items:start }
|
||||
#sentinel-grid.layout-row .col-left { min-width:0 }
|
||||
#sentinel-grid.layout-row .col-right-stack { display:flex;flex-direction:column;gap:12px;min-width:0 }
|
||||
#sentinel-grid.layout-row .col-extra { display:flex;flex-direction:column;gap:8px;padding:0 8px }
|
||||
#sentinel-grid.layout-row .col-extra { display:flex;flex-direction:column;gap:6px;padding:0 8px }
|
||||
#sentinel-grid.layout-row .extra-row { font-size:.8rem;padding:3px 0;border-bottom:1px solid var(--md-sys-color-surface-container-lowest) }
|
||||
#sentinel-grid.layout-row .extra-row .elabel { font-size:.65rem;color:var(--text-dim);text-transform:uppercase;margin-right:4px }
|
||||
#sentinel-grid.layout-row .extra-row .eval { font-weight:500 }
|
||||
#sentinel-grid.layout-row .mtr-data .rlabel { font-size:.7rem;color:var(--text-dim);text-transform:uppercase;margin-right:6px }
|
||||
#sentinel-grid.layout-row .mtr-data .rval { font-weight:600 }
|
||||
#sentinel-grid.layout-row .mtr { display:flex;align-items:center;height:48px;padding:0 8px;margin-bottom:6px;position:relative }
|
||||
@@ -264,6 +267,21 @@
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
// 更新额外指标
|
||||
var loadEl = card.querySelector('.load-val');
|
||||
var swapEl = card.querySelector('.swap-val');
|
||||
var diskEl = card.querySelector('.disk-val');
|
||||
var tempEl = card.querySelector('.temp-val');
|
||||
var trafEl = card.querySelector('.traf-val');
|
||||
var procEl = card.querySelector('.proc-val');
|
||||
var sysData = n.system || {};
|
||||
if (loadEl && sysData.cpu) loadEl.textContent = (sysData.cpu.load_avg||[0,0,0]).map(function(v){return v.toFixed(1);}).join(' / ');
|
||||
if (swapEl && sysData.swap) swapEl.textContent = (sysData.swap.percent||0).toFixed(1)+'% ('+(sysData.swap.used_gb||0).toFixed(1)+'G)';
|
||||
if (diskEl && sysData.disk) diskEl.textContent = (sysData.disk.percent||0).toFixed(1)+'% ('+(sysData.disk.used_gb||0).toFixed(1)+'/'+(sysData.disk.total_gb||0).toFixed(1)+'G)';
|
||||
if (tempEl && sysData.temperature) tempEl.textContent = (sysData.temperature.current||0) > 0 ? (sysData.temperature.current).toFixed(1)+'°C' : '—';
|
||||
if (trafEl && sysData.network) trafEl.textContent = '↓'+(sysData.network.rx_mb||0).toFixed(0)+'M ↑'+(sysData.network.tx_mb||0).toFixed(0)+'M';
|
||||
if (procEl && sysData.process) procEl.textContent = (sysData.process.memory_mb||0).toFixed(0)+'MB';
|
||||
|
||||
var infoR = card.querySelector('.col-info');
|
||||
if (infoR) infoR.textContent = (n.last_seen ? new Date(n.last_seen*1000).toLocaleTimeString() : '—');
|
||||
} else {
|
||||
|
||||
@@ -64,9 +64,12 @@ class SystemInfoCollector:
|
||||
},
|
||||
"cpu": self._get_cpu(),
|
||||
"memory": self._get_memory(),
|
||||
"swap": self._get_swap(),
|
||||
"disk": self._get_disk(),
|
||||
"process": self._get_process(),
|
||||
"network": self._get_network(),
|
||||
"net_speed": self._get_network_speed()
|
||||
"net_speed": self._get_network_speed(),
|
||||
"temperature": self._get_temperature(),
|
||||
}
|
||||
|
||||
def _get_cpu(self) -> Dict[str, Any]:
|
||||
@@ -107,6 +110,71 @@ class SystemInfoCollector:
|
||||
logger.warning(f"Memory info failed: {e}")
|
||||
return {"total_gb": 0, "used_gb": 0, "percent": 0}
|
||||
|
||||
def _get_swap(self) -> Dict[str, Any]:
|
||||
if self.psutil:
|
||||
try:
|
||||
s = self.psutil.swap_memory()
|
||||
return {"total_gb": round(s.total / 1073741824, 1),
|
||||
"used_gb": round(s.used / 1073741824, 1),
|
||||
"percent": s.percent}
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
with open('/proc/meminfo') as f:
|
||||
mem = {}
|
||||
for line in f:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
mem[parts[0].rstrip(':')] = int(parts[1]) * 1024
|
||||
t = mem.get('SwapTotal', 0)
|
||||
f_swap = mem.get('SwapFree', mem.get('SwapCached', 0))
|
||||
if t > 0:
|
||||
used = t - f_swap
|
||||
return {"total_gb": round(t / 1073741824, 1),
|
||||
"used_gb": round(used / 1073741824, 1),
|
||||
"percent": round((used / t) * 100, 1)}
|
||||
except Exception:
|
||||
pass
|
||||
return {"total_gb": 0, "used_gb": 0, "percent": 0}
|
||||
|
||||
def _get_disk(self) -> Dict[str, Any]:
|
||||
if self.psutil:
|
||||
try:
|
||||
d = self.psutil.disk_usage('/')
|
||||
return {"total_gb": round(d.total / 1073741824, 1),
|
||||
"used_gb": round(d.used / 1073741824, 1),
|
||||
"percent": d.percent}
|
||||
except Exception:
|
||||
pass
|
||||
return {"total_gb": 0, "used_gb": 0, "percent": 0}
|
||||
|
||||
def _get_temperature(self) -> Dict[str, Any]:
|
||||
if self.psutil:
|
||||
try:
|
||||
temps = self.psutil.sensors_temperatures()
|
||||
if temps:
|
||||
for name, entries in temps.items():
|
||||
for e in entries:
|
||||
if e.current > 0:
|
||||
return {"name": e.label or name, "current": round(e.current, 1)}
|
||||
except Exception:
|
||||
pass
|
||||
# Android fallback: read thermal zone
|
||||
try:
|
||||
for i in range(10):
|
||||
tz = f'/sys/class/thermal/thermal_zone{i}/temp'
|
||||
ttype = f'/sys/class/thermal/thermal_zone{i}/type'
|
||||
if os.path.exists(tz) and os.path.exists(ttype):
|
||||
with open(ttype) as f:
|
||||
name = f.read().strip()
|
||||
if 'cpu' in name.lower() or 'soc' in name.lower():
|
||||
with open(tz) as f:
|
||||
temp = int(f.read().strip()) / 1000.0
|
||||
return {"name": name, "current": round(temp, 1)}
|
||||
except Exception:
|
||||
pass
|
||||
return {"name": "", "current": 0}
|
||||
|
||||
def _get_process(self) -> Dict[str, Any]:
|
||||
"""Current process info (PID + RSS memory)"""
|
||||
pid = os.getpid()
|
||||
|
||||
Reference in New Issue
Block a user