修改数据源查询逻辑

This commit is contained in:
CN-JS-HuiBai
2026-04-12 17:37:26 +08:00
parent d7ac1bedb4
commit b79cb09987
4 changed files with 93 additions and 35 deletions

View File

@@ -477,12 +477,21 @@
<input type="text" id="sourceDesc" placeholder="数据源描述" autocomplete="off"> <input type="text" id="sourceDesc" placeholder="数据源描述" autocomplete="off">
</div> </div>
<div class="form-group" id="serverSourceOption" <div class="form-group" id="serverSourceOption"
style="display: flex; align-items: flex-end; padding-bottom: 8px;"> style="display: flex; align-items: flex-end; padding-bottom: 8px; flex-wrap: wrap; gap: 12px; margin-top: 4px;">
<label <label
style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap;"> style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap;">
<input type="checkbox" id="isServerSource" checked <input type="checkbox" id="isOverviewSource" checked
style="width: 16px; height: 16px; accent-color: var(--accent-indigo);"> style="width: 16px; height: 16px; accent-color: var(--accent-indigo);">
<span>用于服务器展示</span> <span>加入总览统计</span>
</label>
<label
style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap;">
<input type="checkbox" id="isDetailSource" checked
style="width: 16px; height: 16px; accent-color: var(--accent-indigo);">
<span>加入详情展示</span>
</label>
<label id="isServerSourceContainer" style="display: none;">
<input type="checkbox" id="isServerSource" checked disabled>
</label> </label>
</div> </div>
<div class="form-actions"> <div class="form-actions">

View File

@@ -37,6 +37,8 @@
sourceDesc: document.getElementById('sourceDesc'), sourceDesc: document.getElementById('sourceDesc'),
btnTest: document.getElementById('btnTest'), btnTest: document.getElementById('btnTest'),
btnAdd: document.getElementById('btnAdd'), btnAdd: document.getElementById('btnAdd'),
isOverviewSource: document.getElementById('isOverviewSource'),
isDetailSource: document.getElementById('isDetailSource'),
isServerSource: document.getElementById('isServerSource'), isServerSource: document.getElementById('isServerSource'),
formMessage: document.getElementById('formMessage'), formMessage: document.getElementById('formMessage'),
sourceItems: document.getElementById('sourceItems'), sourceItems: document.getElementById('sourceItems'),
@@ -2382,17 +2384,22 @@
} }
window.editRoute = function (id, source_id, source, dest, target) { 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; editingRouteId = id;
dom.routeSourceSelect.value = source_id; dom.routeSourceSelect.value = route ? route.source_id : (source_id || '');
dom.routeSourceInput.value = source; dom.routeSourceInput.value = route ? route.latency_source : (source || '');
dom.routeDestInput.value = dest; dom.routeDestInput.value = route ? route.latency_dest : (dest || '');
dom.routeTargetInput.value = target; dom.routeTargetInput.value = route ? route.latency_target : (target || '');
dom.btnAddRoute.textContent = '保存修改'; dom.btnAddRoute.textContent = '保存修改';
dom.btnCancelEditRoute.style.display = 'block'; dom.btnCancelEditRoute.style.display = 'block';
// Select the tab just in case (though it's already there) // Select the tab 'latency' (not 'routes')
const tab = Array.from(dom.modalTabs).find(t => t.dataset.tab === 'routes'); const tab = Array.from(dom.modalTabs).find(t => t.dataset.tab === 'latency');
if (tab) tab.click(); if (tab) tab.click();
}; };
@@ -2621,9 +2628,11 @@
<span class="source-status ${source.status === 'online' ? 'source-status-online' : 'source-status-offline'}"> <span class="source-status ${source.status === 'online' ? 'source-status-online' : 'source-status-offline'}">
${source.status === 'online' ? '在线' : '离线'} ${source.status === 'online' ? '在线' : '离线'}
</span> </span>
<span class="source-type-badge ${source.is_server_source ? 'type-server' : 'type-other'}" title="${source.is_server_source ? '该数据源用于展示服务器列表和指标' : '该数据源仅用于特定目的(如 Blackbox 延迟),不参与服务器列表统计'}"> ${source.type === 'blackbox' ? '<span class="source-type-badge type-other">Blackbox</span>' : `
${source.type === 'blackbox' ? 'Blackbox' : (source.is_server_source ? '服务器看板' : '独立数据源')} ${source.is_overview_source ? '<span class="source-type-badge type-server" style="background: var(--accent-indigo);">总览</span>' : ''}
</span> ${source.is_detail_source ? '<span class="source-type-badge type-server" style="background: var(--accent-emerald);">详情</span>' : ''}
${!source.is_overview_source && !source.is_detail_source ? '<span class="source-type-badge type-other">独立数据源</span>' : ''}
`}
</div> </div>
<div class="source-item-url">${escapeHtml(source.url)}</div> <div class="source-item-url">${escapeHtml(source.url)}</div>
${source.description ? `<div class="source-item-desc">${escapeHtml(source.description)}</div>` : ''} ${source.description ? `<div class="source-item-desc">${escapeHtml(source.description)}</div>` : ''}
@@ -2698,13 +2707,20 @@
// ---- Add Source ---- // ---- Add Source ----
let editingSourceId = null; 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; editingSourceId = source.id;
dom.sourceName.value = source.name || ''; dom.sourceName.value = source.name || '';
dom.sourceUrl.value = source.url || ''; dom.sourceUrl.value = source.url || '';
dom.sourceType.value = source.type || 'prometheus'; dom.sourceType.value = source.type || 'prometheus';
dom.sourceDesc.value = source.description || ''; 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 // Toggle Blackbox UI
if (source.type === 'blackbox') { if (source.type === 'blackbox') {
@@ -2736,7 +2752,8 @@
dom.sourceUrl.value = ''; dom.sourceUrl.value = '';
dom.sourceType.value = 'prometheus'; dom.sourceType.value = 'prometheus';
dom.sourceDesc.value = ''; 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.serverSourceOption.style.display = 'flex';
dom.btnAdd.textContent = '添加'; dom.btnAdd.textContent = '添加';
@@ -2757,7 +2774,9 @@
const type = dom.sourceType.value; const type = dom.sourceType.value;
const description = dom.sourceDesc.value.trim(); const description = dom.sourceDesc.value.trim();
// Default to false for blackbox, otherwise use checkbox // 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) { if (!name || !url) {
showMessage('请填写名称和URL', 'error'); showMessage('请填写名称和URL', 'error');
@@ -2775,7 +2794,7 @@
const response = await fetch(urlPath, { const response = await fetch(urlPath, {
method: method, method: method,
headers: { 'Content-Type': 'application/json' }, 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) { if (response.ok) {

View File

@@ -32,6 +32,8 @@ const SCHEMA = {
url VARCHAR(500) NOT NULL, url VARCHAR(500) NOT NULL,
description TEXT, description TEXT,
is_server_source TINYINT(1) DEFAULT 1, 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', type VARCHAR(50) DEFAULT 'prometheus',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE 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: '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: '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: '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: { site_settings: {

View File

@@ -820,8 +820,16 @@ app.post('/api/sources', requireAuth, async (req, res) => {
if (!/^https?:\/\//i.test(url)) url = 'http://' + url; if (!/^https?:\/\//i.test(url)) url = 'http://' + url;
try { try {
const [result] = await db.query( const [result] = await db.query(
'INSERT INTO prometheus_sources (name, url, description, is_server_source, type) VALUES (?, ?, ?, ?, ?)', '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), type || 'prometheus'] [
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]); 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; if (url && !/^https?:\/\//i.test(url)) url = 'http://' + url;
try { try {
await db.query( await db.query(
'UPDATE prometheus_sources SET name = ?, url = ?, description = ?, is_server_source = ?, type = ? WHERE 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, type || 'prometheus', req.params.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 // Clear network history cache
await cache.del('network_history_all'); await cache.del('network_history_all');
@@ -994,7 +1011,7 @@ app.post('/api/settings', requireAuth, async (req, res) => {
// Reusable function to get overview metrics // Reusable function to get overview metrics
async function getOverview(force = false) { 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) { if (sources.length === 0) {
return { return {
totalServers: 0, totalServers: 0,
@@ -1023,7 +1040,12 @@ async function getOverview(force = false) {
try { try {
const metrics = await prometheusService.getOverviewMetrics(source.url, source.name); 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 await cache.set(cacheKey, enrichedMetrics, 15); // Cache for 15s
return enrichedMetrics; return enrichedMetrics;
@@ -1046,14 +1068,16 @@ async function getOverview(force = false) {
let allServers = []; let allServers = [];
for (const m of validMetrics) { for (const m of validMetrics) {
totalServers += m.totalServers; if (m.isOverview) {
activeServers += (m.activeServers !== undefined ? m.activeServers : m.totalServers); totalServers += m.totalServers;
cpuUsed += m.cpu.used; activeServers += (m.activeServers !== undefined ? m.activeServers : m.totalServers);
cpuTotal += m.cpu.total; cpuUsed += m.cpu.used;
memUsed += m.memory.used; cpuTotal += m.cpu.total;
memTotal += m.memory.total; memUsed += m.memory.used;
diskUsed += m.disk.used; memTotal += m.memory.total;
diskTotal += m.disk.total; diskUsed += m.disk.used;
diskTotal += m.disk.total;
}
// Aggregates ONLY for selected network sources // Aggregates ONLY for selected network sources
if (selectedSourceNames.length === 0 || selectedSourceNames.includes(m.sourceName)) { if (selectedSourceNames.length === 0 || selectedSourceNames.includes(m.sourceName)) {
@@ -1063,7 +1087,9 @@ async function getOverview(force = false) {
traffic24hTx += m.traffic24h.tx; traffic24hTx += m.traffic24h.tx;
} }
allServers = allServers.concat(m.servers); if (m.isDetail) {
allServers = allServers.concat(m.servers);
}
} }
const overview = { 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 [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; 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 = []; let params = [];
if (selectedSourcesStr) { if (selectedSourcesStr) {
@@ -1212,7 +1238,7 @@ app.get('/api/metrics/network-history', async (req, res) => {
// Get CPU usage history for sparklines // Get CPU usage history for sparklines
app.get('/api/metrics/cpu-history', async (req, res) => { app.get('/api/metrics/cpu-history', async (req, res) => {
try { 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) { if (sources.length === 0) {
return res.json({ timestamps: [], values: [] }); return res.json({ timestamps: [], values: [] });
} }