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;
}