diff --git a/public/index.html b/public/index.html index 95a2b0c..95c7dca 100644 --- a/public/index.html +++ b/public/index.html @@ -593,7 +593,25 @@ -

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

+

开启后,点击服务器详情时会显示该服务器的公网 IP 地址。

+ + +
+

高级指标配置 (可选)

+ +
+ + +

留空则使用 Prometheus Target 自动发现。若需兼容 node_exporter 自定义指标(如 textfile),请在此输入指标名。

+
+ +
+ + +

从该指标的哪个标签提取 IP?默认为 address

+
diff --git a/public/js/app.js b/public/js/app.js index 3869728..a767168 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -115,6 +115,8 @@ btnExpandGlobe: document.getElementById('btnExpandGlobe'), btnRefreshNetwork: document.getElementById('btnRefreshNetwork'), showServerIpInput: document.getElementById('showServerIpInput'), + ipMetricNameInput: document.getElementById('ipMetricNameInput'), + ipLabelNameInput: document.getElementById('ipLabelNameInput'), // Footer & Filing icpFilingInput: document.getElementById('icpFilingInput'), psFilingInput: document.getElementById('psFilingInput'), @@ -1994,6 +1996,8 @@ // Update IP visibility input if (dom.showServerIpInput) dom.showServerIpInput.value = settings.show_server_ip ? "1" : "0"; + if (dom.ipMetricNameInput) dom.ipMetricNameInput.value = settings.ip_metric_name || ''; + if (dom.ipLabelNameInput) dom.ipLabelNameInput.value = settings.ip_label_name || 'address'; // Sync security tab dependency updateSecurityDependency(); @@ -2112,7 +2116,9 @@ 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(','), - show_server_ip: dom.showServerIpInput ? (dom.showServerIpInput.value === "1") : false + show_server_ip: dom.showServerIpInput ? (dom.showServerIpInput.value === "1") : false, + ip_metric_name: dom.ipMetricNameInput ? dom.ipMetricNameInput.value.trim() : null, + ip_label_name: dom.ipLabelNameInput ? dom.ipLabelNameInput.value.trim() : 'address' }; // UI Feedback for both potential save buttons diff --git a/server/db-schema-check.js b/server/db-schema-check.js index c169bef..ec2909b 100644 --- a/server/db-schema-check.js +++ b/server/db-schema-check.js @@ -66,6 +66,8 @@ const SCHEMA = { ps_filing VARCHAR(255), network_data_sources TEXT, show_server_ip TINYINT(1) DEFAULT 0, + ip_metric_name VARCHAR(100) DEFAULT NULL, + ip_label_name VARCHAR(100) DEFAULT 'address', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `, @@ -132,6 +134,14 @@ const SCHEMA = { { name: 'show_server_ip', sql: "ALTER TABLE site_settings ADD COLUMN show_server_ip TINYINT(1) DEFAULT 0 AFTER network_data_sources" + }, + { + name: 'ip_metric_name', + sql: "ALTER TABLE site_settings ADD COLUMN ip_metric_name VARCHAR(100) DEFAULT NULL AFTER show_server_ip" + }, + { + name: 'ip_label_name', + sql: "ALTER TABLE site_settings ADD COLUMN ip_label_name VARCHAR(100) DEFAULT 'address' AFTER ip_metric_name" } ] }, diff --git a/server/index.js b/server/index.js index 605a564..711218d 100644 --- a/server/index.js +++ b/server/index.js @@ -146,7 +146,9 @@ function getPublicSiteSettings(settings = {}) { icp_filing: settings.icp_filing || null, ps_filing: settings.ps_filing || null, network_data_sources: settings.network_data_sources || null, - show_server_ip: settings.show_server_ip ? 1 : 0 + show_server_ip: settings.show_server_ip ? 1 : 0, + ip_metric_name: settings.ip_metric_name || null, + ip_label_name: settings.ip_label_name || 'address' }; } @@ -551,6 +553,8 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => { ps_filing VARCHAR(255), network_data_sources TEXT, show_server_ip TINYINT(1) DEFAULT 0, + ip_metric_name VARCHAR(100) DEFAULT NULL, + ip_label_name VARCHAR(100) DEFAULT 'address', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); @@ -566,6 +570,8 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => { 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("ALTER TABLE site_settings ADD COLUMN IF NOT EXISTS ip_metric_name VARCHAR(100) DEFAULT NULL AFTER show_server_ip"); + await connection.query("ALTER TABLE site_settings ADD COLUMN IF NOT EXISTS ip_label_name VARCHAR(100) DEFAULT 'address' AFTER ip_metric_name"); await connection.query(` CREATE TABLE IF NOT EXISTS latency_routes ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -923,7 +929,9 @@ app.post('/api/settings', requireAuth, async (req, res) => { 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), - show_server_ip: show_server_ip !== undefined ? (show_server_ip ? 1 : 0) : (current.show_server_ip || 0) + show_server_ip: show_server_ip !== undefined ? (show_server_ip ? 1 : 0) : (current.show_server_ip || 0), + ip_metric_name: ip_metric_name !== undefined ? ip_metric_name : (current.ip_metric_name || null), + ip_label_name: ip_label_name !== undefined ? ip_label_name : (current.ip_label_name || 'address') }; await db.query(` @@ -1216,8 +1224,8 @@ app.get('/api/metrics/server-details', requireServerDetailsAccess, async (req, r } const sourceUrl = rows[0].url; - // Fetch detailed metrics - const details = await prometheusService.getServerDetails(sourceUrl, instance, job); + // Fetch detailed metrics with custom metric configuration if present + const details = await prometheusService.getServerDetails(sourceUrl, instance, job, req.siteSettings); // Dynamic field removal based on security settings: PHYSICAL DATA STRIPPING if (!req.siteSettings || !req.siteSettings.show_server_ip) { diff --git a/server/prometheus-service.js b/server/prometheus-service.js index 2ec9bb4..d0dca3c 100644 --- a/server/prometheus-service.js +++ b/server/prometheus-service.js @@ -535,7 +535,7 @@ function resolveToken(token) { /** * Get detailed metrics for a specific server (node) */ -async function getServerDetails(baseUrl, instance, job) { +async function getServerDetails(baseUrl, instance, job, settings = {}) { const url = normalizeUrl(baseUrl); const node = resolveToken(instance); @@ -582,12 +582,38 @@ async function getServerDetails(baseUrl, instance, job) { await Promise.all(queryPromises); - // Add IP information from Prometheus target (discovered labels or scrape URL) + // Add IP information try { + let foundIp = false; + + // 1. Try Custom Node Exporter Metric if configured + if (settings.ip_metric_name) { + try { + const expr = `${settings.ip_metric_name}{instance="${node}",job="${job}"}`; + const res = await query(url, expr); + if (res && res.length > 0) { + const address = res[0].metric[settings.ip_label_name || 'address']; + if (address) { + if (address.includes(':')) { + results.ipv6 = [address]; + results.ipv4 = []; + } else { + results.ipv4 = [address]; + results.ipv6 = []; + } + foundIp = true; + } + } + } catch (e) { + console.error(`[Prometheus] Error querying custom IP metric ${settings.ip_metric_name}:`, e.message); + } + } + + // 2. Fallback to Prometheus Targets API + if (!foundIp) { const targets = await getTargets(baseUrl); const matchedTarget = targets.find(t => t.labels && t.labels.instance === node && t.labels.job === job); if (matchedTarget) { - // Extract address from scrapeUrl or instance label const scrapeUrl = matchedTarget.scrapeUrl || ''; try { const urlObj = new URL(scrapeUrl); @@ -599,20 +625,29 @@ async function getServerDetails(baseUrl, instance, job) { results.ipv4 = [host]; results.ipv6 = []; } + foundIp = true; } catch (e) { - // Fallback to instance label without port - const inst = matchedTarget.labels.instance.split(':')[0]; - results.ipv4 = [inst]; - results.ipv6 = []; + results.ipv4 = []; + results.ipv6 = []; } - } else { + } else { results.ipv4 = []; results.ipv6 = []; + } + } else { + results.ipv4 = []; + results.ipv6 = []; } - } catch (e) { - console.error(`[Prometheus] Error fetching target info for ${node}:`, e.message); + } + + if (!foundIp) { results.ipv4 = []; results.ipv6 = []; + } + } catch (err) { + console.error(`[Prometheus] Critical error resolving IPs for ${node}:`, err.message); + results.ipv4 = []; + results.ipv6 = []; } // Group partitions