From c9784ec48e608e6c107ff8cee3b5b3707d5ec3b7 Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Fri, 10 Apr 2026 21:27:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=9A=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E6=9F=A5=E7=9C=8B=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=9C=B0?= =?UTF-8?q?=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 9 +++++++++ public/js/app.js | 27 ++++++++++++++++++++++++++- server/db-schema-check.js | 5 +++++ server/index.js | 19 ++++++++++++------- server/prometheus-service.js | 6 +++++- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/public/index.html b/public/index.html index 5eb6200..ac0f432 100644 --- a/public/index.html +++ b/public/index.html @@ -574,6 +574,15 @@ 选择参与 24 小时网络流量统计的 Prometheus 数据源。如果不勾选任何项,则统计所有数据源。 +
+ + +

开启后,点击服务器详情时会显示该服务器的公网 IP 地址(需 node_exporter 提供支持)。

+
diff --git a/public/js/app.js b/public/js/app.js index bc603f3..05746c8 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -113,6 +113,7 @@ globeCard: document.getElementById('globeCard'), btnExpandGlobe: document.getElementById('btnExpandGlobe'), btnRefreshNetwork: document.getElementById('btnRefreshNetwork'), + showServerIpInput: document.getElementById('showServerIpInput'), // Footer & Filing icpFilingInput: document.getElementById('icpFilingInput'), psFilingInput: document.getElementById('psFilingInput'), @@ -539,6 +540,7 @@ if (dom.psFilingInput) dom.psFilingInput.value = window.SITE_SETTINGS.ps_filing || ''; if (dom.logoUrlDarkInput) dom.logoUrlDarkInput.value = window.SITE_SETTINGS.logo_url_dark || ''; if (dom.faviconUrlInput) dom.faviconUrlInput.value = window.SITE_SETTINGS.favicon_url || ''; + if (dom.showServerIpInput) dom.showServerIpInput.value = window.SITE_SETTINGS.show_server_ip ? "1" : "0"; // Latency routes loaded separately in openSettings or on startup } @@ -1592,6 +1594,28 @@ dom.detailDiskTotal.textContent = formatBytes(data.totalDiskSize || 0); } + // IP Addresses + const infoGrid = document.getElementById('detailInfoGrid'); + if (infoGrid) { + // Remove any previously added IP items + infoGrid.querySelectorAll('.info-item-ip').forEach(el => el.remove()); + + if (window.SITE_SETTINGS && window.SITE_SETTINGS.show_server_ip) { + if (data.ipv4 && data.ipv4.length > 0) { + const ipv4Item = document.createElement('div'); + ipv4Item.className = 'info-item info-item-ip'; + ipv4Item.innerHTML = `IPv4 地址${escapeHtml(data.ipv4.join(', '))}`; + infoGrid.appendChild(ipv4Item); + } + if (data.ipv6 && data.ipv6.length > 0) { + const ipv6Item = document.createElement('div'); + ipv6Item.className = 'info-item info-item-ip'; + ipv6Item.innerHTML = `IPv6 地址${escapeHtml(data.ipv6.join(', '))}`; + infoGrid.appendChild(ipv6Item); + } + } + } + // Define metrics to show const cpuValueHtml = `
@@ -2068,7 +2092,8 @@ p95_type: dom.p95TypeSelect ? dom.p95TypeSelect.value : 'tx', ps_filing: dom.psFilingInput ? dom.psFilingInput.value.trim() : '', icp_filing: dom.icpFilingInput ? dom.icpFilingInput.value.trim() : '', - network_data_sources: Array.from(dom.networkSourceSelector.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value).join(',') + network_data_sources: Array.from(dom.networkSourceSelector.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value).join(','), + show_server_ip: dom.showServerIpInput ? (dom.showServerIpInput.value === "1") : false }; dom.btnSaveSiteSettings.disabled = true; diff --git a/server/db-schema-check.js b/server/db-schema-check.js index 0823917..c169bef 100644 --- a/server/db-schema-check.js +++ b/server/db-schema-check.js @@ -65,6 +65,7 @@ const SCHEMA = { icp_filing VARCHAR(255), ps_filing VARCHAR(255), network_data_sources TEXT, + show_server_ip TINYINT(1) DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `, @@ -127,6 +128,10 @@ const SCHEMA = { { name: 'network_data_sources', sql: "ALTER TABLE site_settings ADD COLUMN network_data_sources TEXT AFTER ps_filing" + }, + { + name: 'show_server_ip', + sql: "ALTER TABLE site_settings ADD COLUMN show_server_ip TINYINT(1) DEFAULT 0 AFTER network_data_sources" } ] }, diff --git a/server/index.js b/server/index.js index c432802..e1e3a2c 100644 --- a/server/index.js +++ b/server/index.js @@ -145,7 +145,8 @@ function getPublicSiteSettings(settings = {}) { : 1, icp_filing: settings.icp_filing || null, ps_filing: settings.ps_filing || null, - network_data_sources: settings.network_data_sources || null + network_data_sources: settings.network_data_sources || null, + show_server_ip: settings.show_server_ip ? 1 : 0 }; } @@ -547,6 +548,7 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => { icp_filing VARCHAR(255), ps_filing VARCHAR(255), network_data_sources TEXT, + show_server_ip TINYINT(1) DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); @@ -561,6 +563,7 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => { await connection.query("ALTER TABLE site_settings ADD COLUMN IF NOT EXISTS show_page_name TINYINT(1) DEFAULT 1 AFTER page_name"); await connection.query("ALTER TABLE site_settings ADD COLUMN IF NOT EXISTS require_login_for_server_details TINYINT(1) DEFAULT 1 AFTER p95_type"); await connection.query("ALTER TABLE site_settings ADD COLUMN IF NOT EXISTS network_data_sources TEXT AFTER ps_filing"); + await connection.query("ALTER TABLE site_settings ADD COLUMN IF NOT EXISTS show_server_ip TINYINT(1) DEFAULT 0 AFTER network_data_sources"); await connection.query(` CREATE TABLE IF NOT EXISTS latency_routes ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -894,7 +897,7 @@ app.post('/api/settings', requireAuth, async (req, res) => { const { page_name, show_page_name, title, logo_url, logo_url_dark, favicon_url, default_theme, show_95_bandwidth, p95_type, require_login_for_server_details, - icp_filing, ps_filing, network_data_sources + icp_filing, ps_filing, network_data_sources, show_server_ip } = req.body; // 3. Prepare parameters, prioritizing body but falling back to current @@ -917,7 +920,8 @@ app.post('/api/settings', requireAuth, async (req, res) => { latency_target: current.latency_target || null, icp_filing: icp_filing !== undefined ? icp_filing : (current.icp_filing || null), ps_filing: ps_filing !== undefined ? ps_filing : (current.ps_filing || null), - network_data_sources: network_data_sources !== undefined ? network_data_sources : (current.network_data_sources || null) + network_data_sources: network_data_sources !== undefined ? network_data_sources : (current.network_data_sources || null), + show_server_ip: show_server_ip !== undefined ? (show_server_ip ? 1 : 0) : (current.show_server_ip || 0) }; await db.query(` @@ -925,8 +929,8 @@ app.post('/api/settings', requireAuth, async (req, res) => { id, page_name, show_page_name, title, logo_url, logo_url_dark, favicon_url, default_theme, show_95_bandwidth, p95_type, require_login_for_server_details, blackbox_source_id, latency_source, latency_dest, latency_target, - icp_filing, ps_filing, network_data_sources - ) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + icp_filing, ps_filing, network_data_sources, show_server_ip + ) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE page_name = VALUES(page_name), show_page_name = VALUES(show_page_name), @@ -944,12 +948,13 @@ app.post('/api/settings', requireAuth, async (req, res) => { latency_target = VALUES(latency_target), icp_filing = VALUES(icp_filing), ps_filing = VALUES(ps_filing), - network_data_sources = VALUES(network_data_sources)`, + network_data_sources = VALUES(network_data_sources), + show_server_ip = VALUES(show_server_ip)`, [ settings.page_name, settings.show_page_name, settings.title, settings.logo_url, settings.logo_url_dark, settings.favicon_url, settings.default_theme, settings.show_95_bandwidth, settings.p95_type, settings.require_login_for_server_details, settings.blackbox_source_id, settings.latency_source, settings.latency_dest, settings.latency_target, - settings.icp_filing, settings.ps_filing, settings.network_data_sources + settings.icp_filing, settings.ps_filing, settings.network_data_sources, settings.show_server_ip ] ); diff --git a/server/prometheus-service.js b/server/prometheus-service.js index 41ad658..c1897e6 100644 --- a/server/prometheus-service.js +++ b/server/prometheus-service.js @@ -559,7 +559,9 @@ async function getServerDetails(baseUrl, instance, job) { sockstatTcpMem: `node_sockstat_TCP_mem{instance="${node}",job="${job}"} * 4096`, // Get individual partitions (excluding virtual and FUSE mounts) partitions_size: `node_filesystem_size_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|autofs|proc|sysfs|fuse.*", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*"}`, - partitions_free: `node_filesystem_free_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|autofs|proc|sysfs|fuse.*", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*"}` + partitions_free: `node_filesystem_free_bytes{instance="${node}", job="${job}", fstype!~"tmpfs|autofs|proc|sysfs|fuse.*", mountpoint!~"/tmp.*|/var/lib/docker/.*|/run/.*"}`, + ipv4: `node_network_address_info{instance="${node}", job="${job}", address!~"127\\\\..*|10\\\\..*|172\\\\.(1[6-9]|2[0-9]|3[0-1])\\\\..*|192\\\\.168\\\\..*", address=~"\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+"}`, + ipv6: `node_network_address_info{instance="${node}", job="${job}", address=~".*:.*", address!~"fe80:.*|::1"}` }; const results = {}; @@ -571,6 +573,8 @@ async function getServerDetails(baseUrl, instance, job) { mountpoint: r.metric.mountpoint, value: parseFloat(r.value[1]) || 0 })); + } else if (key === 'ipv4' || key === 'ipv6') { + results[key] = res.map(r => r.metric.address).filter(a => a); } else { results[key] = res.length > 0 ? parseFloat(res[0].value[1]) : 0; }