From b79cb099876618ec0dea9ffecaf4ce78090cca5f Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sun, 12 Apr 2026 17:37:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 15 ++++++++-- public/js/app.js | 47 +++++++++++++++++++++--------- server/db-schema-check.js | 6 +++- server/index.js | 60 ++++++++++++++++++++++++++++----------- 4 files changed, 93 insertions(+), 35 deletions(-) diff --git a/public/index.html b/public/index.html index 45ab952..c376a32 100644 --- a/public/index.html +++ b/public/index.html @@ -477,12 +477,21 @@
+ style="display: flex; align-items: flex-end; padding-bottom: 8px; flex-wrap: wrap; gap: 12px; margin-top: 4px;"> + +
diff --git a/public/js/app.js b/public/js/app.js index ef57932..ee1853f 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -37,6 +37,8 @@ sourceDesc: document.getElementById('sourceDesc'), btnTest: document.getElementById('btnTest'), btnAdd: document.getElementById('btnAdd'), + isOverviewSource: document.getElementById('isOverviewSource'), + isDetailSource: document.getElementById('isDetailSource'), isServerSource: document.getElementById('isServerSource'), formMessage: document.getElementById('formMessage'), sourceItems: document.getElementById('sourceItems'), @@ -2382,17 +2384,22 @@ } window.editRoute = function (id, source_id, source, dest, target) { + let route = null; + if (source_id === undefined && allStoredLatencyRoutes) { + route = allStoredLatencyRoutes.find(r => r.id === id); + } + editingRouteId = id; - dom.routeSourceSelect.value = source_id; - dom.routeSourceInput.value = source; - dom.routeDestInput.value = dest; - dom.routeTargetInput.value = target; + dom.routeSourceSelect.value = route ? route.source_id : (source_id || ''); + dom.routeSourceInput.value = route ? route.latency_source : (source || ''); + dom.routeDestInput.value = route ? route.latency_dest : (dest || ''); + dom.routeTargetInput.value = route ? route.latency_target : (target || ''); dom.btnAddRoute.textContent = '保存修改'; dom.btnCancelEditRoute.style.display = 'block'; - // Select the tab just in case (though it's already there) - const tab = Array.from(dom.modalTabs).find(t => t.dataset.tab === 'routes'); + // Select the tab 'latency' (not 'routes') + const tab = Array.from(dom.modalTabs).find(t => t.dataset.tab === 'latency'); if (tab) tab.click(); }; @@ -2621,9 +2628,11 @@ ${source.status === 'online' ? '在线' : '离线'} - - ${source.type === 'blackbox' ? 'Blackbox' : (source.is_server_source ? '服务器看板' : '独立数据源')} - + ${source.type === 'blackbox' ? 'Blackbox' : ` + ${source.is_overview_source ? '总览' : ''} + ${source.is_detail_source ? '详情' : ''} + ${!source.is_overview_source && !source.is_detail_source ? '独立数据源' : ''} + `}
${escapeHtml(source.url)}
${source.description ? `
${escapeHtml(source.description)}
` : ''} @@ -2698,13 +2707,20 @@ // ---- Add Source ---- let editingSourceId = null; - window.editSource = function(source) { + window.editSource = function(sourceOrId) { + let source = sourceOrId; + if (typeof sourceOrId !== 'object' && allStoredSources) { + source = allStoredSources.find(s => s.id === sourceOrId); + } + if (!source) return; + editingSourceId = source.id; dom.sourceName.value = source.name || ''; dom.sourceUrl.value = source.url || ''; dom.sourceType.value = source.type || 'prometheus'; dom.sourceDesc.value = source.description || ''; - dom.isServerSource.checked = !!source.is_server_source; + if (dom.isOverviewSource) dom.isOverviewSource.checked = !!source.is_overview_source; + if (dom.isDetailSource) dom.isDetailSource.checked = !!source.is_detail_source; // Toggle Blackbox UI if (source.type === 'blackbox') { @@ -2736,7 +2752,8 @@ dom.sourceUrl.value = ''; dom.sourceType.value = 'prometheus'; dom.sourceDesc.value = ''; - dom.isServerSource.checked = true; + if (dom.isOverviewSource) dom.isOverviewSource.checked = true; + if (dom.isDetailSource) dom.isDetailSource.checked = true; dom.serverSourceOption.style.display = 'flex'; dom.btnAdd.textContent = '添加'; @@ -2757,7 +2774,9 @@ const type = dom.sourceType.value; const description = dom.sourceDesc.value.trim(); // Default to false for blackbox, otherwise use checkbox - const is_server_source = type === 'blackbox' ? false : dom.isServerSource.checked; + const is_overview_source = type === 'blackbox' ? false : dom.isOverviewSource.checked; + const is_detail_source = type === 'blackbox' ? false : dom.isDetailSource.checked; + const is_server_source = is_overview_source || is_detail_source; if (!name || !url) { showMessage('请填写名称和URL', 'error'); @@ -2775,7 +2794,7 @@ const response = await fetch(urlPath, { method: method, headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, url, description, is_server_source, type }) + body: JSON.stringify({ name, url, description, is_server_source, is_overview_source, is_detail_source, type }) }); if (response.ok) { diff --git a/server/db-schema-check.js b/server/db-schema-check.js index e37d5ff..c30a2ea 100644 --- a/server/db-schema-check.js +++ b/server/db-schema-check.js @@ -32,6 +32,8 @@ const SCHEMA = { url VARCHAR(500) NOT NULL, description TEXT, is_server_source TINYINT(1) DEFAULT 1, + is_overview_source TINYINT(1) DEFAULT 1, + is_detail_source TINYINT(1) DEFAULT 1, type VARCHAR(50) DEFAULT 'prometheus', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP @@ -42,7 +44,9 @@ const SCHEMA = { { name: 'url', sql: "ALTER TABLE prometheus_sources ADD COLUMN url VARCHAR(500) NOT NULL AFTER name" }, { name: 'description', sql: "ALTER TABLE prometheus_sources ADD COLUMN description TEXT AFTER url" }, { name: 'is_server_source', sql: "ALTER TABLE prometheus_sources ADD COLUMN is_server_source TINYINT(1) DEFAULT 1 AFTER description" }, - { name: 'type', sql: "ALTER TABLE prometheus_sources ADD COLUMN type VARCHAR(50) DEFAULT 'prometheus' AFTER is_server_source" } + { name: 'is_overview_source', sql: "ALTER TABLE prometheus_sources ADD COLUMN is_overview_source TINYINT(1) DEFAULT 1 AFTER is_server_source" }, + { name: 'is_detail_source', sql: "ALTER TABLE prometheus_sources ADD COLUMN is_detail_source TINYINT(1) DEFAULT 1 AFTER is_overview_source" }, + { name: 'type', sql: "ALTER TABLE prometheus_sources ADD COLUMN type VARCHAR(50) DEFAULT 'prometheus' AFTER is_detail_source" } ] }, site_settings: { diff --git a/server/index.js b/server/index.js index bb161bc..d1de74c 100644 --- a/server/index.js +++ b/server/index.js @@ -820,8 +820,16 @@ app.post('/api/sources', requireAuth, async (req, res) => { if (!/^https?:\/\//i.test(url)) url = 'http://' + url; try { const [result] = await db.query( - 'INSERT INTO prometheus_sources (name, url, description, is_server_source, type) VALUES (?, ?, ?, ?, ?)', - [name, url, description || '', is_server_source === undefined ? 1 : (is_server_source ? 1 : 0), type || 'prometheus'] + 'INSERT INTO prometheus_sources (name, url, description, is_server_source, is_overview_source, is_detail_source, type) VALUES (?, ?, ?, ?, ?, ?, ?)', + [ + name, + url, + description || '', + is_server_source === undefined ? 1 : (is_server_source ? 1 : 0), + req.body.is_overview_source === undefined ? 1 : (req.body.is_overview_source ? 1 : 0), + req.body.is_detail_source === undefined ? 1 : (req.body.is_detail_source ? 1 : 0), + type || 'prometheus' + ] ); const [rows] = await db.query('SELECT * FROM prometheus_sources WHERE id = ?', [result.insertId]); @@ -841,8 +849,17 @@ app.put('/api/sources/:id', requireAuth, async (req, res) => { if (url && !/^https?:\/\//i.test(url)) url = 'http://' + url; try { await db.query( - 'UPDATE prometheus_sources SET name = ?, url = ?, description = ?, is_server_source = ?, type = ? WHERE id = ?', - [name, url, description || '', is_server_source ? 1 : 0, type || 'prometheus', req.params.id] + 'UPDATE prometheus_sources SET name = ?, url = ?, description = ?, is_server_source = ?, is_overview_source = ?, is_detail_source = ?, type = ? WHERE id = ?', + [ + name, + url, + description || '', + is_server_source ? 1 : 0, + req.body.is_overview_source ? 1 : 0, + req.body.is_detail_source ? 1 : 0, + type || 'prometheus', + req.params.id + ] ); // Clear network history cache await cache.del('network_history_all'); @@ -994,7 +1011,7 @@ app.post('/api/settings', requireAuth, async (req, res) => { // Reusable function to get overview metrics async function getOverview(force = false) { - const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"'); + const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE (is_overview_source = 1 OR is_detail_source = 1) AND type != "blackbox"'); if (sources.length === 0) { return { totalServers: 0, @@ -1023,7 +1040,12 @@ async function getOverview(force = false) { try { const metrics = await prometheusService.getOverviewMetrics(source.url, source.name); - const enrichedMetrics = { ...metrics, sourceName: source.name }; + const enrichedMetrics = { + ...metrics, + sourceName: source.name, + isOverview: !!source.is_overview_source, + isDetail: !!source.is_detail_source + }; await cache.set(cacheKey, enrichedMetrics, 15); // Cache for 15s return enrichedMetrics; @@ -1046,14 +1068,16 @@ async function getOverview(force = false) { let allServers = []; for (const m of validMetrics) { - totalServers += m.totalServers; - activeServers += (m.activeServers !== undefined ? m.activeServers : m.totalServers); - cpuUsed += m.cpu.used; - cpuTotal += m.cpu.total; - memUsed += m.memory.used; - memTotal += m.memory.total; - diskUsed += m.disk.used; - diskTotal += m.disk.total; + if (m.isOverview) { + totalServers += m.totalServers; + activeServers += (m.activeServers !== undefined ? m.activeServers : m.totalServers); + cpuUsed += m.cpu.used; + cpuTotal += m.cpu.total; + memUsed += m.memory.used; + memTotal += m.memory.total; + diskUsed += m.disk.used; + diskTotal += m.disk.total; + } // Aggregates ONLY for selected network sources if (selectedSourceNames.length === 0 || selectedSourceNames.includes(m.sourceName)) { @@ -1063,7 +1087,9 @@ async function getOverview(force = false) { traffic24hTx += m.traffic24h.tx; } - allServers = allServers.concat(m.servers); + if (m.isDetail) { + allServers = allServers.concat(m.servers); + } } const overview = { @@ -1172,7 +1198,7 @@ app.get('/api/metrics/network-history', async (req, res) => { const [settingsRows] = await db.query('SELECT network_data_sources FROM site_settings WHERE id = 1'); const selectedSourcesStr = settingsRows.length > 0 ? settingsRows[0].network_data_sources : null; - let query = 'SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"'; + let query = 'SELECT * FROM prometheus_sources WHERE is_overview_source = 1 AND type != "blackbox"'; let params = []; if (selectedSourcesStr) { @@ -1212,7 +1238,7 @@ app.get('/api/metrics/network-history', async (req, res) => { // Get CPU usage history for sparklines app.get('/api/metrics/cpu-history', async (req, res) => { try { - const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"'); + const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_overview_source = 1 AND type != "blackbox"'); if (sources.length === 0) { return res.json({ timestamps: [], values: [] }); }