From 52381672121a9601621874e41b7e9f1ff25e66a0 Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sun, 5 Apr 2026 17:23:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=A1=AC=E7=9B=98=E8=AF=86?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install.sh | 14 ++++----- public/css/style.css | 56 ++++++++++++++++++++++++++++++++++++ public/index.html | 5 ++++ public/js/app.js | 22 +++++++++++++- server/prometheus-service.js | 47 ++++++++++++++++++++++++------ 5 files changed, 128 insertions(+), 16 deletions(-) diff --git a/install.sh b/install.sh index cad14ab..802fa9c 100644 --- a/install.sh +++ b/install.sh @@ -66,7 +66,7 @@ if [ $? -ne 0 ]; then fi # 7. Create Systemd Service File -SERVICE_FILE="/etc/systemd/system/data-wall.service" +SERVICE_FILE="/etc/systemd/system/promdatapanel.service" NODE_PATH=$(command -v node) echo -e "${BLUE}Creating systemd service at $SERVICE_FILE... (May require password)${NC}" @@ -85,7 +85,7 @@ Restart=always RestartSec=10 StandardOutput=syslog StandardError=syslog -SyslogIdentifier=data-wall +SyslogIdentifier=promdatapanel # Pass environment via .env file injection EnvironmentFile=-$PROJECT_DIR/.env Environment=NODE_ENV=production @@ -102,21 +102,21 @@ EOF" # 8. Reload Systemd and Start echo -e "${BLUE}Reloading systemd and restarting service... (May require password)${NC}" sudo systemctl daemon-reload -sudo systemctl enable data-wall -sudo systemctl restart data-wall +sudo systemctl enable promdatapanel +sudo systemctl restart promdatapanel # 9. Check Status echo -e "${BLUE}Checking service status...${NC}" sleep 2 -if sudo systemctl is-active --quiet data-wall; then +if sudo systemctl is-active --quiet promdatapanel; then echo -e "${GREEN}SUCCESS: Service is now running.${NC}" PORT=$(grep "^PORT=" .env | cut -d'=' -f2) PORT=${PORT:-3000} echo -e "Dashboard URL: ${YELLOW}http://localhost:${PORT}${NC}" - echo -e "View logs: ${BLUE}journalctl -u data-wall -f${NC}" + echo -e "View logs: ${BLUE}journalctl -u promdatapanel -f${NC}" else echo -e "${RED}FAILED: Service failed to start.${NC}" - echo -e "Check logs with: ${BLUE}journalctl -u data-wall -xe${NC}" + echo -e "Check logs with: ${BLUE}journalctl -u promdatapanel -xe${NC}" fi # 10. Reverse Proxy Configuration diff --git a/public/css/style.css b/public/css/style.css index 51752d9..1d9dc00 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1259,6 +1259,62 @@ input:checked+.slider:before { color: var(--text-primary); } +.detail-partitions-container { + padding: 20px; + border-top: 1px solid var(--border-color); +} + +.detail-section-title { + font-size: 0.8rem; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 12px; +} + +.detail-partitions-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.partition-row { + display: flex; + flex-direction: column; + gap: 6px; +} + +.partition-info { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.82rem; +} + +.partition-mount { + font-family: var(--font-mono); + color: var(--text-primary); +} + +.partition-usage-text { + color: var(--text-secondary); +} + +.partition-progress { + width: 100%; + height: 6px; + background: rgba(255, 255, 255, 0.05); + border-radius: 3px; + overflow: hidden; +} + +.partition-bar { + height: 100%; + background: var(--accent-amber); + border-radius: 3px; +} + .chevron-icon { width: 20px; height: 20px; diff --git a/public/index.html b/public/index.html index f9c572b..fb0994c 100644 --- a/public/index.html +++ b/public/index.html @@ -479,6 +479,11 @@ 0天 0小时 + + diff --git a/public/js/app.js b/public/js/app.js index 4d358da..a715d96 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -83,7 +83,9 @@ globeTotalNodes: document.getElementById('globeTotalNodes'), globeTotalRegions: document.getElementById('globeTotalRegions'), sourceFilter: document.getElementById('sourceFilter'), - btnResetSort: document.getElementById('btnResetSort') + btnResetSort: document.getElementById('btnResetSort'), + detailPartitionsContainer: document.getElementById('detailPartitionsContainer'), + detailPartitionsList: document.getElementById('detailPartitionsList') }; // ---- State ---- @@ -870,6 +872,24 @@ (IO Wait: ${data.cpuIowait.toFixed(1)}%) `; + + // Render partitions list if any + if (data.partitions && data.partitions.length > 0) { + dom.detailPartitionsContainer.style.display = 'block'; + dom.detailPartitionsList.innerHTML = data.partitions.map(p => ` +
+
+ ${escapeHtml(p.mountpoint)} + ${formatBytes(p.used)} / ${formatBytes(p.size)} (${formatPercent(p.percent)}) +
+
+
+
+
+ `).join(''); + } else { + dom.detailPartitionsContainer.style.display = 'none'; + } const metrics = [ { key: 'cpuBusy', label: 'CPU 使用率', value: cpuValueHtml }, diff --git a/server/prometheus-service.js b/server/prometheus-service.js index e786b67..ee2b705 100644 --- a/server/prometheus-service.js +++ b/server/prometheus-service.js @@ -198,10 +198,10 @@ async function getOverviewMetrics(url, sourceName) { query(url, 'node_memory_MemTotal_bytes').catch(() => []), // Memory available per instance query(url, 'node_memory_MemAvailable_bytes').catch(() => []), - // Disk total per instance (root filesystem + /data) - query(url, 'sum by (instance, job) (node_filesystem_size_bytes{mountpoint=~"/|/data",fstype!="tmpfs"})').catch(() => []), - // Disk free per instance (root filesystem + /data) - query(url, 'sum by (instance, job) (node_filesystem_free_bytes{mountpoint=~"/|/data",fstype!="tmpfs"})').catch(() => []), + // Disk total per instance (excluding virtual fs and restricted paths) + query(url, 'sum by (instance, job) (node_filesystem_size_bytes{fstype!~"tmpfs|overlay|autofs|binfmt_misc|configfs|debugfs|fusectl|hugetlbfs|mqueue|proc|pstore|securityfs|sysfs|devpts|devtmpfs|nsfs|rpc_pipefs|selinuxfs|squashfs|tracefs", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*|/sys/.*|/dev/.*"})').catch(() => []), + // Disk free per instance + query(url, 'sum by (instance, job) (node_filesystem_free_bytes{fstype!~"tmpfs|overlay|autofs|binfmt_misc|configfs|debugfs|fusectl|hugetlbfs|mqueue|proc|pstore|securityfs|sysfs|devpts|devtmpfs|nsfs|rpc_pipefs|selinuxfs|squashfs|tracefs", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*|/sys/.*|/dev/.*"})').catch(() => []), // Network receive rate (bytes/sec) query(url, 'sum by (instance, job) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[1m]))').catch(() => []), // Network transmit rate (bytes/sec) @@ -533,28 +533,59 @@ async function getServerDetails(baseUrl, instance, job) { sysLoad: `node_load1{instance="${node}",job="${job}"} * 100 / count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`, memUsedPct: `(1 - (node_memory_MemAvailable_bytes{instance="${node}", job="${job}"} / node_memory_MemTotal_bytes{instance="${node}", job="${job}"})) * 100`, swapUsedPct: `((node_memory_SwapTotal_bytes{instance="${node}",job="${job}"} - node_memory_SwapFree_bytes{instance="${node}",job="${job}"}) / (node_memory_SwapTotal_bytes{instance="${node}",job="${job}"})) * 100`, - rootFsUsedPct: `100 - ((node_filesystem_avail_bytes{instance="${node}",job="${job}",mountpoint="/",fstype!="rootfs"} * 100) / node_filesystem_size_bytes{instance="${node}",job="${job}",mountpoint="/",fstype!="rootfs"})`, + rootFsUsedPct: `100 - ((node_filesystem_avail_bytes{instance="${node}",job="${job}",mountpoint="/",fstype!~"rootfs|tmpfs"} * 100) / node_filesystem_size_bytes{instance="${node}",job="${job}",mountpoint="/",fstype!~"rootfs|tmpfs"})`, cpuCores: `count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`, memTotal: `node_memory_MemTotal_bytes{instance="${node}",job="${job}"}`, uptime: `node_time_seconds{instance="${node}",job="${job}"} - node_boot_time_seconds{instance="${node}",job="${job}"}`, netRx: `sum(rate(node_network_receive_bytes_total{instance="${node}",job="${job}",device!~'tap.*|veth.*|br.*|docker.*|virbr*|podman.*|lo.*|vmbr.*|fwbr.|ip.*|gre.*|virbr.*|vnet.*'}[1m]))`, netTx: `sum(rate(node_network_transmit_bytes_total{instance="${node}",job="${job}",device!~'tap.*|veth.*|br.*|docker.*|virbr*|podman.*|lo.*|vmbr.*|fwbr.|ip.*|gre.*|virbr.*|vnet.*'}[1m]))`, sockstatTcp: `node_sockstat_TCP_inuse{instance="${node}",job="${job}"}`, - sockstatTcpMem: `node_sockstat_TCP_mem{instance="${node}",job="${job}"} * 4096` // Converting pages to bytes (assuming 4KB pages) + sockstatTcpMem: `node_sockstat_TCP_mem{instance="${node}",job="${job}"} * 4096`, + // Get individual partitions + partitions_size: `node_filesystem_size_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|overlay|autofs|binfmt_misc|configfs|debugfs|fusectl|hugetlbfs|mqueue|proc|pstore|securityfs|sysfs|devpts|devtmpfs|nsfs|rpc_pipefs|selinuxfs|squashfs|tracefs", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*|/sys/.*|/dev/.*"}`, + partitions_free: `node_filesystem_free_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|overlay|autofs|binfmt_misc|configfs|debugfs|fusectl|hugetlbfs|mqueue|proc|pstore|securityfs|sysfs|devpts|devtmpfs|nsfs|rpc_pipefs|selinuxfs|squashfs|tracefs", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*|/sys/.*|/dev/.*"}` }; const results = {}; const queryPromises = Object.entries(queries).map(async ([key, expr]) => { try { const res = await query(url, expr); - results[key] = res.length > 0 ? parseFloat(res[0].value[1]) : 0; + if (key.startsWith('partitions_')) { + results[key] = res.map(r => ({ + mountpoint: r.metric.mountpoint, + value: parseFloat(r.value[1]) || 0 + })); + } else { + results[key] = res.length > 0 ? parseFloat(res[0].value[1]) : 0; + } } catch (e) { console.error(`[Prometheus] Error querying ${key} for ${node}:`, e.message); - results[key] = 0; + results[key] = key.startsWith('partitions_') ? [] : 0; } }); await Promise.all(queryPromises); + + // Group partitions + const partitionsMap = {}; + (results.partitions_size || []).forEach(p => { + partitionsMap[p.mountpoint] = { mountpoint: p.mountpoint, size: p.value, free: 0 }; + }); + (results.partitions_free || []).forEach(p => { + if (partitionsMap[p.mountpoint]) { + partitionsMap[p.mountpoint].free = p.value; + } + }); + + results.partitions = Object.values(partitionsMap).map(p => ({ + ...p, + used: p.size - p.free, + percent: p.size > 0 ? ((p.size - p.free) / p.size * 100) : 0 + })).sort((a, b) => a.mountpoint.localeCompare(b.mountpoint)); + + delete results.partitions_size; + delete results.partitions_free; + return results; }