优化硬盘识别
This commit is contained in:
14
install.sh
14
install.sh
@@ -66,7 +66,7 @@ if [ $? -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 7. Create Systemd Service File
|
# 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)
|
NODE_PATH=$(command -v node)
|
||||||
|
|
||||||
echo -e "${BLUE}Creating systemd service at $SERVICE_FILE... (May require password)${NC}"
|
echo -e "${BLUE}Creating systemd service at $SERVICE_FILE... (May require password)${NC}"
|
||||||
@@ -85,7 +85,7 @@ Restart=always
|
|||||||
RestartSec=10
|
RestartSec=10
|
||||||
StandardOutput=syslog
|
StandardOutput=syslog
|
||||||
StandardError=syslog
|
StandardError=syslog
|
||||||
SyslogIdentifier=data-wall
|
SyslogIdentifier=promdatapanel
|
||||||
# Pass environment via .env file injection
|
# Pass environment via .env file injection
|
||||||
EnvironmentFile=-$PROJECT_DIR/.env
|
EnvironmentFile=-$PROJECT_DIR/.env
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
@@ -102,21 +102,21 @@ EOF"
|
|||||||
# 8. Reload Systemd and Start
|
# 8. Reload Systemd and Start
|
||||||
echo -e "${BLUE}Reloading systemd and restarting service... (May require password)${NC}"
|
echo -e "${BLUE}Reloading systemd and restarting service... (May require password)${NC}"
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable data-wall
|
sudo systemctl enable promdatapanel
|
||||||
sudo systemctl restart data-wall
|
sudo systemctl restart promdatapanel
|
||||||
|
|
||||||
# 9. Check Status
|
# 9. Check Status
|
||||||
echo -e "${BLUE}Checking service status...${NC}"
|
echo -e "${BLUE}Checking service status...${NC}"
|
||||||
sleep 2
|
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}"
|
echo -e "${GREEN}SUCCESS: Service is now running.${NC}"
|
||||||
PORT=$(grep "^PORT=" .env | cut -d'=' -f2)
|
PORT=$(grep "^PORT=" .env | cut -d'=' -f2)
|
||||||
PORT=${PORT:-3000}
|
PORT=${PORT:-3000}
|
||||||
echo -e "Dashboard URL: ${YELLOW}http://localhost:${PORT}${NC}"
|
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
|
else
|
||||||
echo -e "${RED}FAILED: Service failed to start.${NC}"
|
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
|
fi
|
||||||
|
|
||||||
# 10. Reverse Proxy Configuration
|
# 10. Reverse Proxy Configuration
|
||||||
|
|||||||
@@ -1259,6 +1259,62 @@ input:checked+.slider:before {
|
|||||||
color: var(--text-primary);
|
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 {
|
.chevron-icon {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|||||||
@@ -479,6 +479,11 @@
|
|||||||
<span class="info-value" id="detailUptime">0天 0小时</span>
|
<span class="info-value" id="detailUptime">0天 0小时</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-partitions-container" id="detailPartitionsContainer" style="display: none;">
|
||||||
|
<h3 class="detail-section-title">磁盘分区详情 (已挂载)</h3>
|
||||||
|
<div class="detail-partitions-list" id="detailPartitionsList"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,7 +83,9 @@
|
|||||||
globeTotalNodes: document.getElementById('globeTotalNodes'),
|
globeTotalNodes: document.getElementById('globeTotalNodes'),
|
||||||
globeTotalRegions: document.getElementById('globeTotalRegions'),
|
globeTotalRegions: document.getElementById('globeTotalRegions'),
|
||||||
sourceFilter: document.getElementById('sourceFilter'),
|
sourceFilter: document.getElementById('sourceFilter'),
|
||||||
btnResetSort: document.getElementById('btnResetSort')
|
btnResetSort: document.getElementById('btnResetSort'),
|
||||||
|
detailPartitionsContainer: document.getElementById('detailPartitionsContainer'),
|
||||||
|
detailPartitionsList: document.getElementById('detailPartitionsList')
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- State ----
|
// ---- State ----
|
||||||
@@ -870,6 +872,24 @@
|
|||||||
<span style="font-size: 0.7rem; color: var(--text-secondary); font-weight: normal;">(IO Wait: ${data.cpuIowait.toFixed(1)}%)</span>
|
<span style="font-size: 0.7rem; color: var(--text-secondary); font-weight: normal;">(IO Wait: ${data.cpuIowait.toFixed(1)}%)</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Render partitions list if any
|
||||||
|
if (data.partitions && data.partitions.length > 0) {
|
||||||
|
dom.detailPartitionsContainer.style.display = 'block';
|
||||||
|
dom.detailPartitionsList.innerHTML = data.partitions.map(p => `
|
||||||
|
<div class="partition-row">
|
||||||
|
<div class="partition-info">
|
||||||
|
<span class="partition-mount">${escapeHtml(p.mountpoint)}</span>
|
||||||
|
<span class="partition-usage-text">${formatBytes(p.used)} / ${formatBytes(p.size)} (${formatPercent(p.percent)})</span>
|
||||||
|
</div>
|
||||||
|
<div class="partition-progress">
|
||||||
|
<div class="partition-bar" style="width: ${Math.min(p.percent, 100)}%; background-color: ${getUsageColor(p.percent)};"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} else {
|
||||||
|
dom.detailPartitionsContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
const metrics = [
|
const metrics = [
|
||||||
{ key: 'cpuBusy', label: 'CPU 使用率', value: cpuValueHtml },
|
{ key: 'cpuBusy', label: 'CPU 使用率', value: cpuValueHtml },
|
||||||
|
|||||||
@@ -198,10 +198,10 @@ async function getOverviewMetrics(url, sourceName) {
|
|||||||
query(url, 'node_memory_MemTotal_bytes').catch(() => []),
|
query(url, 'node_memory_MemTotal_bytes').catch(() => []),
|
||||||
// Memory available per instance
|
// Memory available per instance
|
||||||
query(url, 'node_memory_MemAvailable_bytes').catch(() => []),
|
query(url, 'node_memory_MemAvailable_bytes').catch(() => []),
|
||||||
// Disk total per instance (root filesystem + /data)
|
// Disk total per instance (excluding virtual fs and restricted paths)
|
||||||
query(url, 'sum by (instance, job) (node_filesystem_size_bytes{mountpoint=~"/|/data",fstype!="tmpfs"})').catch(() => []),
|
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 (root filesystem + /data)
|
// Disk free per instance
|
||||||
query(url, 'sum by (instance, job) (node_filesystem_free_bytes{mountpoint=~"/|/data",fstype!="tmpfs"})').catch(() => []),
|
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)
|
// Network receive rate (bytes/sec)
|
||||||
query(url, 'sum by (instance, job) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[1m]))').catch(() => []),
|
query(url, 'sum by (instance, job) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*|br-.*"}[1m]))').catch(() => []),
|
||||||
// Network transmit rate (bytes/sec)
|
// 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))`,
|
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`,
|
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`,
|
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))`,
|
cpuCores: `count(count(node_cpu_seconds_total{instance="${node}",job="${job}"}) by (cpu))`,
|
||||||
memTotal: `node_memory_MemTotal_bytes{instance="${node}",job="${job}"}`,
|
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}"}`,
|
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]))`,
|
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]))`,
|
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}"}`,
|
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 results = {};
|
||||||
const queryPromises = Object.entries(queries).map(async ([key, expr]) => {
|
const queryPromises = Object.entries(queries).map(async ([key, expr]) => {
|
||||||
try {
|
try {
|
||||||
const res = await query(url, expr);
|
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) {
|
} catch (e) {
|
||||||
console.error(`[Prometheus] Error querying ${key} for ${node}:`, e.message);
|
console.error(`[Prometheus] Error querying ${key} for ${node}:`, e.message);
|
||||||
results[key] = 0;
|
results[key] = key.startsWith('partitions_') ? [] : 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(queryPromises);
|
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;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user