From 528410d3e9e7c7b61ad9d598d7d2178f27020138 Mon Sep 17 00:00:00 2001 From: qinglong Date: Sun, 14 Jun 2026 09:31:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A3=81=E7=9B=98=E5=88=86=E5=8C=BA?= =?UTF-8?q?=E5=88=97=20=E2=80=94=20=E5=A4=9A=E5=88=86=E5=8C=BA/=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=9D=A1/overflow/=E8=BF=87=E6=BB=A4=E8=99=9A?= =?UTF-8?q?=E6=8B=9FFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SystemInfoCollector: - _get_partitions(): psutil disk_partitions + 过滤虚拟FS - 跳过 tmpfs/devtmpfs/proc/sysfs/cgroup等22种 - 无psutil时 fallback到根分区 前端4列: 160px 1fr 0.8fr 1fr - col-disk: 分区名/挂载点/进度条/百分比 - max-height:200px overflow:auto - 跨平台兼容 Co-Authored-By: Claude --- plugins/sentinel/dashboard.html | 36 ++++++++++++++++--- services/web_panel/utils/system_info.py | 48 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/plugins/sentinel/dashboard.html b/plugins/sentinel/dashboard.html index 50cf7f5..34a6e0c 100644 --- a/plugins/sentinel/dashboard.html +++ b/plugins/sentinel/dashboard.html @@ -56,13 +56,22 @@ .node-notes { font-size:.75rem; color:var(--text-dim); font-style:italic; margin-bottom:8px } /* 横版布局 — 左设备信息 | 右数据+图表同行 */ #sentinel-grid.layout-row { display:flex;flex-direction:column;gap:12px } - #sentinel-grid.layout-row .node-card { display:grid;grid-template-columns:160px 1fr 1fr;gap:12px 16px;align-items:start } + #sentinel-grid.layout-row .node-card { display:grid;grid-template-columns:160px 1fr 0.8fr 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: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 .col-disk { overflow-y:auto;max-height:200px;padding:0 4px } + #sentinel-grid.layout-row .disk-list { display:flex;flex-direction:column;gap:4px } + #sentinel-grid.layout-row .part-row { display:flex;flex-direction:column;gap:2px;padding:3px 0;border-bottom:1px solid var(--md-sys-color-surface-container-lowest) } + #sentinel-grid.layout-row .part-info { display:flex;justify-content:space-between;font-size:.7rem } + #sentinel-grid.layout-row .part-name { font-weight:500;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap } + #sentinel-grid.layout-row .part-mount { color:var(--text-dim);font-size:.65rem } + #sentinel-grid.layout-row .part-bar { height:3px;border-radius:1px;background:var(--md-sys-color-surface-container-lowest);margin:2px 0 } + #sentinel-grid.layout-row .part-bar-fill { height:100%;border-radius:1px;background:var(--primary);transition:width .6s } + #sentinel-grid.layout-row .part-pct { font-size:.6rem;color:var(--text-dim);text-align:right } #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 } @@ -201,11 +210,11 @@ '
'+ '
负载
'+ '
SWAP
'+ - '
磁盘
'+ '
温度
'+ '
流量
'+ '
进程
'+ - '
'; + ''+ + '
'; } else { card.innerHTML = '
'+(n.online?'🟢':'🔴')+' '+esc(n.name)+'
'+ @@ -285,7 +294,26 @@ 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) { var st = sysData.swap.total_gb||0; swapEl.textContent = st > 0 ? (sysData.swap.percent||0).toFixed(1)+'% ('+(sysData.swap.used_gb||0).toFixed(1)+'G)' : '—'; } - if (diskEl && sysData.disk) { var dt = sysData.disk.total_gb||0; diskEl.textContent = dt > 0 ? (sysData.disk.percent||0).toFixed(1)+'% ('+(sysData.disk.used_gb||0).toFixed(1)+'/'+(sysData.disk.total_gb||0).toFixed(1)+'G)' : '—'; } + // 磁盘分区列表 + var diskList = card.querySelector('.disk-list'); + if (diskList && sysData.partitions) { + var parts = sysData.partitions || []; + if (!diskList.querySelector('.part-row') || diskList.children.length !== parts.length) { + diskList.innerHTML = parts.map(function(p){ + return '
'+esc(p.device||'?')+' '+esc(p.mount||'')+'
'+(p.percent||0).toFixed(0)+'%
'; + }).join(''); + } else { + var rows = diskList.querySelectorAll('.part-row'); + parts.forEach(function(p, i){ + if (rows[i]) { + var fill = rows[i].querySelector('.part-bar-fill'); + var pct = rows[i].querySelector('.part-pct'); + if (fill) fill.style.width = (p.percent||0)+'%'; + if (pct) pct.textContent = (p.percent||0).toFixed(0)+'%'; + } + }); + } + } 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'; diff --git a/services/web_panel/utils/system_info.py b/services/web_panel/utils/system_info.py index f80336c..ae16f87 100644 --- a/services/web_panel/utils/system_info.py +++ b/services/web_panel/utils/system_info.py @@ -66,6 +66,7 @@ class SystemInfoCollector: "memory": self._get_memory(), "swap": self._get_swap(), "disk": self._get_disk(), + "partitions": self._get_partitions(), "process": self._get_process(), "network": self._get_network(), "net_speed": self._get_network_speed(), @@ -137,6 +138,53 @@ class SystemInfoCollector: pass return {"total_gb": 0, "used_gb": 0, "percent": 0} + _SKIP_FS = {'tmpfs', 'devtmpfs', 'devfs', 'overlay', 'squashfs', 'proc', 'sysfs', + 'cgroup', 'cgroup2', 'debugfs', 'tracefs', 'fusectl', 'configfs', + 'securityfs', 'pstore', 'efivarfs', 'autofs', 'ramfs', 'hugetlbfs', + 'mqueue', 'bpf', 'binfmt_misc', 'rpc_pipefs', 'nfsd', 'smb3fs_ctl', + 'snapfuse', 'fuse.gvfsd-fuse', 'fuse.portal'} + + def _get_partitions(self) -> list: + """获取存储分区列表 (过滤虚拟文件系统)""" + parts = [] + if self.psutil: + try: + for p in self.psutil.disk_partitions(all=False): + if p.fstype and p.fstype.lower() in self._SKIP_FS: + continue + try: + usage = self.psutil.disk_usage(p.mountpoint) + parts.append({ + "device": p.device, + "mount": p.mountpoint, + "fstype": p.fstype or "", + "total_gb": round(usage.total / 1073741824, 1), + "used_gb": round(usage.used / 1073741824, 1), + "percent": usage.percent, + }) + except (PermissionError, OSError): + parts.append({ + "device": p.device, + "mount": p.mountpoint, + "fstype": p.fstype or "", + "total_gb": 0, "used_gb": 0, "percent": 0, + }) + except Exception: + pass + if not parts: + # Fallback: just root partition + disk = self._get_disk() + if disk.get("total_gb", 0) > 0: + parts.append({ + "device": "/", + "mount": "/", + "fstype": "", + "total_gb": disk["total_gb"], + "used_gb": disk["used_gb"], + "percent": disk["percent"], + }) + return parts + def _get_disk(self) -> Dict[str, Any]: if self.psutil: try: