优化数据库查询布局
This commit is contained in:
@@ -2803,3 +2803,101 @@ input:checked+.slider:before {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Source Settings Toggles ---- */
|
||||||
|
.source-options-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-option-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-option-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-option-item:hover .source-option-label {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-option-label {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 38px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-label {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
inset: 0;
|
||||||
|
background-color: var(--bg-input);
|
||||||
|
transition: .35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-label:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
background-color: var(--text-muted);
|
||||||
|
transition: .35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input:checked + .switch-label {
|
||||||
|
background-color: rgba(99, 102, 241, 0.15);
|
||||||
|
border-color: var(--accent-indigo);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-input:checked + .switch-label:before {
|
||||||
|
transform: translateX(18px);
|
||||||
|
background-color: var(--accent-indigo);
|
||||||
|
box-shadow: 0 0 10px rgba(99, 102, 241, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light theme support */
|
||||||
|
:root.light-theme .source-options-grid {
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.light-theme .source-option-item:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.light-theme .switch-label:before {
|
||||||
|
background-color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -476,23 +476,25 @@
|
|||||||
<label for="sourceDesc">描述 (可选)</label>
|
<label for="sourceDesc">描述 (可选)</label>
|
||||||
<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 form-group-wide" id="serverSourceOption">
|
||||||
style="display: flex; align-items: flex-end; padding-bottom: 8px; flex-wrap: wrap; gap: 12px; margin-top: 4px;">
|
<label style="margin-bottom: 8px;">数据源偏好设置</label>
|
||||||
<label
|
<div class="source-options-grid">
|
||||||
style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap;">
|
<label class="source-option-item" title="将此数据源的服务器指标聚合到首页总览中">
|
||||||
<input type="checkbox" id="isOverviewSource" checked
|
<div class="switch-wrapper">
|
||||||
style="width: 16px; height: 16px; accent-color: var(--accent-indigo);">
|
<input type="checkbox" id="isOverviewSource" checked class="switch-input">
|
||||||
<span>加入总览统计</span>
|
<div class="switch-label"></div>
|
||||||
</label>
|
</div>
|
||||||
<label
|
<span class="source-option-label">加入总览统计</span>
|
||||||
style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap;">
|
</label>
|
||||||
<input type="checkbox" id="isDetailSource" checked
|
<label class="source-option-item" title="在服务器详情列表中显示此数据源的服务器">
|
||||||
style="width: 16px; height: 16px; accent-color: var(--accent-indigo);">
|
<div class="switch-wrapper">
|
||||||
<span>加入详情展示</span>
|
<input type="checkbox" id="isDetailSource" checked class="switch-input">
|
||||||
</label>
|
<div class="switch-label"></div>
|
||||||
<label id="isServerSourceContainer" style="display: none;">
|
</div>
|
||||||
<input type="checkbox" id="isServerSource" checked disabled>
|
<span class="source-option-label">加入详情展示</span>
|
||||||
</label>
|
</label>
|
||||||
|
<input type="checkbox" id="isServerSource" checked disabled style="display: none;">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn btn-test" id="btnTest">测试连接</button>
|
<button class="btn btn-test" id="btnTest">测试连接</button>
|
||||||
@@ -582,13 +584,6 @@
|
|||||||
<label for="icpFilingInput">ICP 备案号 (如:京ICP备12345678号)</label>
|
<label for="icpFilingInput">ICP 备案号 (如:京ICP备12345678号)</label>
|
||||||
<input type="text" id="icpFilingInput" placeholder="请输入 ICP 备案号">
|
<input type="text" id="icpFilingInput" placeholder="请输入 ICP 备案号">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-top: 15px;">
|
|
||||||
<label>网络流量趋势 (24h) 统计数据源</label>
|
|
||||||
<div id="network-source-selector" class="network-source-list" style="margin-top: 8px; display: flex; flex-wrap: wrap; gap: 10px; background: var(--bg-input); padding: 12px; border-radius: var(--radius-sm); border: 1px solid var(--border-color);">
|
|
||||||
<div class="loading-inline" style="color: var(--text-muted); font-size: 0.9rem;">加载数据源中...</div>
|
|
||||||
</div>
|
|
||||||
<small style="display: block; margin-top: 6px; color: var(--text-muted);">选择参与 24 小时网络流量统计的 Prometheus 数据源。如果不勾选任何项,则统计所有数据源。</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end;">
|
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end;">
|
||||||
<button class="btn btn-add" id="btnSaveSiteSettings">保存基础设置</button>
|
<button class="btn btn-add" id="btnSaveSiteSettings">保存基础设置</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -103,7 +103,6 @@
|
|||||||
oldPasswordInput: document.getElementById('oldPassword'),
|
oldPasswordInput: document.getElementById('oldPassword'),
|
||||||
newPasswordInput: document.getElementById('newPassword'),
|
newPasswordInput: document.getElementById('newPassword'),
|
||||||
confirmNewPasswordInput: document.getElementById('confirmNewPassword'),
|
confirmNewPasswordInput: document.getElementById('confirmNewPassword'),
|
||||||
networkSourceSelector: document.getElementById('network-source-selector'),
|
|
||||||
btnChangePassword: document.getElementById('btnChangePassword'),
|
btnChangePassword: document.getElementById('btnChangePassword'),
|
||||||
changePasswordMessage: document.getElementById('changePasswordMessage'),
|
changePasswordMessage: document.getElementById('changePasswordMessage'),
|
||||||
globeContainer: document.getElementById('globeContainer'),
|
globeContainer: document.getElementById('globeContainer'),
|
||||||
@@ -276,7 +275,7 @@
|
|||||||
dom.serverSourceOption.style.display = 'none';
|
dom.serverSourceOption.style.display = 'none';
|
||||||
dom.isServerSource.checked = false;
|
dom.isServerSource.checked = false;
|
||||||
} else {
|
} else {
|
||||||
dom.serverSourceOption.style.display = 'flex';
|
dom.serverSourceOption.style.display = '';
|
||||||
dom.isServerSource.checked = true;
|
dom.isServerSource.checked = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -618,6 +617,7 @@
|
|||||||
checkAuthStatus();
|
checkAuthStatus();
|
||||||
|
|
||||||
// Start data fetching
|
// Start data fetching
|
||||||
|
loadSources();
|
||||||
fetchMetrics();
|
fetchMetrics();
|
||||||
fetchNetworkHistory();
|
fetchNetworkHistory();
|
||||||
fetchLatency();
|
fetchLatency();
|
||||||
@@ -2104,21 +2104,6 @@
|
|||||||
if (dom.showPageNameInput) dom.showPageNameInput.value = settings.show_page_name !== undefined ? settings.show_page_name.toString() : "1";
|
if (dom.showPageNameInput) dom.showPageNameInput.value = settings.show_page_name !== undefined ? settings.show_page_name.toString() : "1";
|
||||||
if (dom.requireLoginForServerDetailsInput) dom.requireLoginForServerDetailsInput.value = settings.require_login_for_server_details ? "1" : "0";
|
if (dom.requireLoginForServerDetailsInput) dom.requireLoginForServerDetailsInput.value = settings.require_login_for_server_details ? "1" : "0";
|
||||||
|
|
||||||
// Handle network data sources checkboxes
|
|
||||||
if (settings.network_data_sources) {
|
|
||||||
const selected = settings.network_data_sources.split(',').map(s => s.trim());
|
|
||||||
const checkboxes = dom.networkSourceSelector.querySelectorAll('input[type="checkbox"]');
|
|
||||||
checkboxes.forEach(cb => {
|
|
||||||
cb.checked = selected.includes(cb.value);
|
|
||||||
});
|
|
||||||
// We'll also store this in a temporary place because loadSources might run later
|
|
||||||
dom.networkSourceSelector.dataset.pendingSelected = settings.network_data_sources;
|
|
||||||
} else {
|
|
||||||
const checkboxes = dom.networkSourceSelector.querySelectorAll('input[type="checkbox"]');
|
|
||||||
checkboxes.forEach(cb => cb.checked = false);
|
|
||||||
dom.networkSourceSelector.dataset.pendingSelected = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Theme Priority: localStorage > Site Default
|
// Handle Theme Priority: localStorage > Site Default
|
||||||
const savedTheme = localStorage.getItem('theme');
|
const savedTheme = localStorage.getItem('theme');
|
||||||
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
||||||
@@ -2278,7 +2263,6 @@
|
|||||||
p95_type: dom.p95TypeSelect ? dom.p95TypeSelect.value : 'tx',
|
p95_type: dom.p95TypeSelect ? dom.p95TypeSelect.value : 'tx',
|
||||||
ps_filing: dom.psFilingInput ? dom.psFilingInput.value.trim() : '',
|
ps_filing: dom.psFilingInput ? dom.psFilingInput.value.trim() : '',
|
||||||
icp_filing: dom.icpFilingInput ? dom.icpFilingInput.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_metric_name: dom.ipMetricNameInput ? dom.ipMetricNameInput.value.trim() : null,
|
||||||
ip_label_name: dom.ipLabelNameInput ? dom.ipLabelNameInput.value.trim() : 'address',
|
ip_label_name: dom.ipLabelNameInput ? dom.ipLabelNameInput.value.trim() : 'address',
|
||||||
@@ -2596,8 +2580,14 @@
|
|||||||
|
|
||||||
async function loadSources() {
|
async function loadSources() {
|
||||||
try {
|
try {
|
||||||
|
if (dom.sourceItems) {
|
||||||
|
dom.sourceItems.innerHTML = '<div class="source-loading"><div class="dot dot-pulse"></div><span>正在加载数据源...</span></div>';
|
||||||
|
}
|
||||||
const response = await fetch('/api/sources');
|
const response = await fetch('/api/sources');
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
|
if (dom.sourceItems) {
|
||||||
|
dom.sourceItems.innerHTML = '<div class="source-empty">请登录后管理数据源</div>';
|
||||||
|
}
|
||||||
promptLogin('登录后可查看和管理数据源');
|
promptLogin('登录后可查看和管理数据源');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2608,7 +2598,6 @@
|
|||||||
if (dom.totalServersLabel) dom.totalServersLabel.textContent = `服务器总数 (${promSources.length} 数据源)`;
|
if (dom.totalServersLabel) dom.totalServersLabel.textContent = `服务器总数 (${promSources.length} 数据源)`;
|
||||||
updateSourceFilterOptions(sourcesArray);
|
updateSourceFilterOptions(sourcesArray);
|
||||||
renderSources(sourcesArray);
|
renderSources(sourcesArray);
|
||||||
renderNetworkSourceSelector(sourcesArray);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading sources:', err);
|
console.error('Error loading sources:', err);
|
||||||
}
|
}
|
||||||
@@ -2645,26 +2634,6 @@
|
|||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNetworkSourceSelector(sources) {
|
|
||||||
if (!dom.networkSourceSelector) return;
|
|
||||||
|
|
||||||
// Only show Prometheus sources for filtering
|
|
||||||
const promSources = sources.filter(s => s.type !== 'blackbox');
|
|
||||||
|
|
||||||
if (promSources.length === 0) {
|
|
||||||
dom.networkSourceSelector.innerHTML = '<div style="color: var(--text-muted); font-size: 0.9rem;">暂无可用数据源</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingSelected = dom.networkSourceSelector.dataset.pendingSelected ? dom.networkSourceSelector.dataset.pendingSelected.split(',').map(s => s.trim()) : [];
|
|
||||||
|
|
||||||
dom.networkSourceSelector.innerHTML = promSources.map(source => `
|
|
||||||
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; padding: 4px 8px; border-radius: 4px; background: rgba(255,255,255,0.05); font-size: 0.9rem;">
|
|
||||||
<input type="checkbox" value="${escapeHtml(source.name)}" ${pendingSelected.includes(source.name) ? 'checked' : ''} style="cursor: pointer;">
|
|
||||||
<span>${escapeHtml(source.name)}</span>
|
|
||||||
</label>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Test Connection ----
|
// ---- Test Connection ----
|
||||||
async function testConnection() {
|
async function testConnection() {
|
||||||
@@ -2726,7 +2695,7 @@
|
|||||||
if (source.type === 'blackbox') {
|
if (source.type === 'blackbox') {
|
||||||
dom.serverSourceOption.style.display = 'none';
|
dom.serverSourceOption.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
dom.serverSourceOption.style.display = 'flex';
|
dom.serverSourceOption.style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
dom.btnAdd.textContent = '保存修改';
|
dom.btnAdd.textContent = '保存修改';
|
||||||
@@ -2754,7 +2723,7 @@
|
|||||||
dom.sourceDesc.value = '';
|
dom.sourceDesc.value = '';
|
||||||
if (dom.isOverviewSource) dom.isOverviewSource.checked = true;
|
if (dom.isOverviewSource) dom.isOverviewSource.checked = true;
|
||||||
if (dom.isDetailSource) dom.isDetailSource.checked = true;
|
if (dom.isDetailSource) dom.isDetailSource.checked = true;
|
||||||
dom.serverSourceOption.style.display = 'flex';
|
dom.serverSourceOption.style.display = '';
|
||||||
dom.btnAdd.textContent = '添加';
|
dom.btnAdd.textContent = '添加';
|
||||||
|
|
||||||
const cancelBtn = document.getElementById('btnCancelEditSource');
|
const cancelBtn = document.getElementById('btnCancelEditSource');
|
||||||
|
|||||||
@@ -797,7 +797,8 @@ app.get('/api/sources', requireAuth, async (req, res) => {
|
|||||||
const res = await fetch(`${source.url.replace(/\/+$/, '')}/metrics`, { timeout: 3000 }).catch(() => null);
|
const res = await fetch(`${source.url.replace(/\/+$/, '')}/metrics`, { timeout: 3000 }).catch(() => null);
|
||||||
response = (res && res.ok) ? 'Blackbox Exporter Ready' : 'Connection Error';
|
response = (res && res.ok) ? 'Blackbox Exporter Ready' : 'Connection Error';
|
||||||
} else {
|
} else {
|
||||||
response = await prometheusService.testConnection(source.url);
|
// Use a shorter timeout for list view to prevent blocking UI
|
||||||
|
response = await prometheusService.testConnection(source.url, 2500);
|
||||||
}
|
}
|
||||||
return { ...source, status: 'online', version: response };
|
return { ...source, status: 'online', version: response };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -954,7 +955,6 @@ app.post('/api/settings', requireAuth, async (req, res) => {
|
|||||||
latency_target: current.latency_target || null,
|
latency_target: current.latency_target || null,
|
||||||
icp_filing: icp_filing !== undefined ? icp_filing : (current.icp_filing || null),
|
icp_filing: icp_filing !== undefined ? icp_filing : (current.icp_filing || null),
|
||||||
ps_filing: ps_filing !== undefined ? ps_filing : (current.ps_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_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'),
|
ip_label_name: ip_label_name !== undefined ? ip_label_name : (current.ip_label_name || 'address'),
|
||||||
@@ -995,7 +995,7 @@ app.post('/api/settings', requireAuth, async (req, res) => {
|
|||||||
settings.page_name, settings.show_page_name, settings.title, settings.logo_url, settings.logo_url_dark, settings.favicon_url,
|
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.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.blackbox_source_id, settings.latency_source, settings.latency_dest, settings.latency_target,
|
||||||
settings.icp_filing, settings.ps_filing, settings.network_data_sources, settings.show_server_ip,
|
settings.icp_filing, settings.ps_filing, settings.show_server_ip,
|
||||||
settings.ip_metric_name, settings.ip_label_name, settings.custom_metrics
|
settings.ip_metric_name, settings.ip_label_name, settings.custom_metrics
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -1011,7 +1011,9 @@ 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) {
|
||||||
|
// Fetch sources: overview OR detail
|
||||||
const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE (is_overview_source = 1 OR is_detail_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,
|
||||||
@@ -1024,11 +1026,6 @@ async function getOverview(force = false) {
|
|||||||
servers: []
|
servers: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 selectedSourceNames = selectedSourcesStr ? selectedSourcesStr.split(',').map(s => s.trim()).filter(s => s) : [];
|
|
||||||
|
|
||||||
const allMetrics = await Promise.all(sources.map(async (source) => {
|
const allMetrics = await Promise.all(sources.map(async (source) => {
|
||||||
const cacheKey = `source_metrics:${source.url}:${source.name}`;
|
const cacheKey = `source_metrics:${source.url}:${source.name}`;
|
||||||
if (force) {
|
if (force) {
|
||||||
@@ -1077,10 +1074,6 @@ async function getOverview(force = false) {
|
|||||||
memTotal += m.memory.total;
|
memTotal += m.memory.total;
|
||||||
diskUsed += m.disk.used;
|
diskUsed += m.disk.used;
|
||||||
diskTotal += m.disk.total;
|
diskTotal += m.disk.total;
|
||||||
}
|
|
||||||
|
|
||||||
// Aggregates ONLY for selected network sources
|
|
||||||
if (selectedSourceNames.length === 0 || selectedSourceNames.includes(m.sourceName)) {
|
|
||||||
netRx += m.network.rx;
|
netRx += m.network.rx;
|
||||||
netTx += m.network.tx;
|
netTx += m.network.tx;
|
||||||
traffic24hRx += m.traffic24h.rx;
|
traffic24hRx += m.traffic24h.rx;
|
||||||
@@ -1195,21 +1188,8 @@ app.get('/api/metrics/network-history', async (req, res) => {
|
|||||||
if (cached) return res.json(cached);
|
if (cached) return res.json(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [settingsRows] = await db.query('SELECT network_data_sources FROM site_settings WHERE id = 1');
|
const query = 'SELECT * FROM prometheus_sources WHERE is_overview_source = 1 AND type != "blackbox"';
|
||||||
const selectedSourcesStr = settingsRows.length > 0 ? settingsRows[0].network_data_sources : null;
|
const [sources] = await db.query(query);
|
||||||
|
|
||||||
let query = 'SELECT * FROM prometheus_sources WHERE is_overview_source = 1 AND type != "blackbox"';
|
|
||||||
let params = [];
|
|
||||||
|
|
||||||
if (selectedSourcesStr) {
|
|
||||||
const selectedSourceNames = selectedSourcesStr.split(',').map(s => s.trim()).filter(s => s);
|
|
||||||
if (selectedSourceNames.length > 0) {
|
|
||||||
query += ' AND name IN (?)';
|
|
||||||
params.push(selectedSourceNames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [sources] = await db.query(query, params);
|
|
||||||
if (sources.length === 0) {
|
if (sources.length === 0) {
|
||||||
return res.json({ timestamps: [], rx: [], tx: [] });
|
return res.json({ timestamps: [], rx: [], tx: [] });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ function createClient(baseUrl) {
|
|||||||
/**
|
/**
|
||||||
* Test Prometheus connection
|
* Test Prometheus connection
|
||||||
*/
|
*/
|
||||||
async function testConnection(url) {
|
async function testConnection(url, customTimeout = null) {
|
||||||
const normalized = normalizeUrl(url);
|
const normalized = normalizeUrl(url);
|
||||||
try {
|
try {
|
||||||
// Using native fetch to avoid follow-redirects/axios "protocol mismatch" issues in some Node environments
|
// Using native fetch to avoid follow-redirects/axios "protocol mismatch" issues in some Node environments
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timer = setTimeout(() => controller.abort(), QUERY_TIMEOUT);
|
const timer = setTimeout(() => controller.abort(), customTimeout || QUERY_TIMEOUT);
|
||||||
|
|
||||||
// Node native fetch - handles http/https automatically
|
// Node native fetch - handles http/https automatically
|
||||||
const res = await fetch(`${normalized}/api/v1/status/buildinfo`, {
|
const res = await fetch(`${normalized}/api/v1/status/buildinfo`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user