添加对指定数据源统计带宽
This commit is contained in:
@@ -567,6 +567,13 @@
|
||||
<label for="icpFilingInput">ICP 备案号 (如:京ICP备12345678号)</label>
|
||||
<input type="text" id="icpFilingInput" placeholder="请输入 ICP 备案号">
|
||||
</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;">
|
||||
<button class="btn btn-add" id="btnSaveSiteSettings">保存基础设置</button>
|
||||
</div>
|
||||
@@ -589,6 +596,7 @@
|
||||
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary);">
|
||||
<option value="">-- 选择数据源 --</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>起航点</label>
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
oldPasswordInput: document.getElementById('oldPassword'),
|
||||
newPasswordInput: document.getElementById('newPassword'),
|
||||
confirmNewPasswordInput: document.getElementById('confirmNewPassword'),
|
||||
networkSourceSelector: document.getElementById('network-source-selector'),
|
||||
btnChangePassword: document.getElementById('btnChangePassword'),
|
||||
changePasswordMessage: document.getElementById('changePasswordMessage'),
|
||||
globeContainer: document.getElementById('globeContainer'),
|
||||
@@ -1908,6 +1909,21 @@
|
||||
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";
|
||||
|
||||
// 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
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
||||
@@ -2033,7 +2049,8 @@
|
||||
show_95_bandwidth: dom.show95BandwidthInput ? (dom.show95BandwidthInput.value === "1") : false,
|
||||
p95_type: dom.p95TypeSelect ? dom.p95TypeSelect.value : 'tx',
|
||||
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(',')
|
||||
};
|
||||
|
||||
dom.btnSaveSiteSettings.disabled = true;
|
||||
@@ -2317,6 +2334,7 @@
|
||||
if (dom.totalServersLabel) dom.totalServersLabel.textContent = `服务器总数 (${promSources.length} 数据源)`;
|
||||
updateSourceFilterOptions(sourcesArray);
|
||||
renderSources(sourcesArray);
|
||||
renderNetworkSourceSelector(sourcesArray);
|
||||
} catch (err) {
|
||||
console.error('Error loading sources:', err);
|
||||
}
|
||||
@@ -2351,6 +2369,27 @@
|
||||
`).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 ----
|
||||
async function testConnection() {
|
||||
const url = dom.sourceUrl.value.trim();
|
||||
|
||||
@@ -64,6 +64,7 @@ const SCHEMA = {
|
||||
latency_target VARCHAR(255),
|
||||
icp_filing VARCHAR(255),
|
||||
ps_filing VARCHAR(255),
|
||||
network_data_sources TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`,
|
||||
@@ -122,6 +123,10 @@ const SCHEMA = {
|
||||
{
|
||||
name: 'ps_filing',
|
||||
sql: "ALTER TABLE site_settings ADD COLUMN ps_filing VARCHAR(255) AFTER icp_filing"
|
||||
},
|
||||
{
|
||||
name: 'network_data_sources',
|
||||
sql: "ALTER TABLE site_settings ADD COLUMN network_data_sources TEXT AFTER ps_filing"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -144,7 +144,8 @@ function getPublicSiteSettings(settings = {}) {
|
||||
? (settings.require_login_for_server_details ? 1 : 0)
|
||||
: 1,
|
||||
icp_filing: settings.icp_filing || null,
|
||||
ps_filing: settings.ps_filing || null
|
||||
ps_filing: settings.ps_filing || null,
|
||||
network_data_sources: settings.network_data_sources || null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -545,6 +546,7 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => {
|
||||
latency_target VARCHAR(255),
|
||||
icp_filing VARCHAR(255),
|
||||
ps_filing VARCHAR(255),
|
||||
network_data_sources TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
@@ -558,6 +560,7 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => {
|
||||
await connection.query("ALTER TABLE prometheus_sources ADD COLUMN IF NOT EXISTS type VARCHAR(50) DEFAULT 'prometheus' AFTER is_server_source");
|
||||
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(`
|
||||
CREATE TABLE IF NOT EXISTS latency_routes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
@@ -727,7 +730,8 @@ const serveIndex = async (req, res) => {
|
||||
latency_dest: null,
|
||||
latency_target: null,
|
||||
icp_filing: null,
|
||||
ps_filing: null
|
||||
ps_filing: null,
|
||||
network_data_sources: null
|
||||
};
|
||||
|
||||
if (isDbInitialized) {
|
||||
@@ -873,25 +877,6 @@ app.get('/api/settings', async (req, res) => {
|
||||
return res.json(getPublicSiteSettings());
|
||||
}
|
||||
return res.json(getPublicSiteSettings(rows[0]));
|
||||
if (rows.length === 0) {
|
||||
return res.json({
|
||||
page_name: '数据可视化展示大屏',
|
||||
show_page_name: 1,
|
||||
title: '数据可视化展示大屏',
|
||||
logo_url: null,
|
||||
logo_url_dark: null,
|
||||
favicon_url: null,
|
||||
show_95_bandwidth: 0,
|
||||
p95_type: 'tx',
|
||||
blackbox_source_id: null,
|
||||
latency_source: null,
|
||||
latency_dest: null,
|
||||
latency_target: null,
|
||||
icp_filing: null,
|
||||
ps_filing: null
|
||||
});
|
||||
}
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
console.error('Error fetching settings:', err);
|
||||
res.status(500).json({ error: 'Failed to fetch settings' });
|
||||
@@ -909,7 +894,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
|
||||
icp_filing, ps_filing, network_data_sources
|
||||
} = req.body;
|
||||
|
||||
// 3. Prepare parameters, prioritizing body but falling back to current
|
||||
@@ -926,22 +911,22 @@ app.post('/api/settings', requireAuth, async (req, res) => {
|
||||
require_login_for_server_details: require_login_for_server_details !== undefined
|
||||
? (require_login_for_server_details ? 1 : 0)
|
||||
: (current.require_login_for_server_details !== undefined ? current.require_login_for_server_details : 1),
|
||||
blackbox_source_id: current.blackbox_source_id || null, // UI doesn't send this
|
||||
latency_source: current.latency_source || null, // UI doesn't send this
|
||||
latency_dest: current.latency_dest || null, // UI doesn't send this
|
||||
latency_target: current.latency_target || null, // UI doesn't send this
|
||||
blackbox_source_id: current.blackbox_source_id || null,
|
||||
latency_source: current.latency_source || null,
|
||||
latency_dest: current.latency_dest || null,
|
||||
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)
|
||||
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)
|
||||
};
|
||||
|
||||
// 4. Update database
|
||||
await db.query(
|
||||
`INSERT INTO site_settings (
|
||||
await db.query(`
|
||||
INSERT INTO site_settings (
|
||||
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
|
||||
) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
icp_filing, ps_filing, network_data_sources
|
||||
) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
page_name = VALUES(page_name),
|
||||
show_page_name = VALUES(show_page_name),
|
||||
@@ -958,12 +943,13 @@ app.post('/api/settings', requireAuth, async (req, res) => {
|
||||
latency_dest = VALUES(latency_dest),
|
||||
latency_target = VALUES(latency_target),
|
||||
icp_filing = VALUES(icp_filing),
|
||||
ps_filing = VALUES(ps_filing)`,
|
||||
ps_filing = VALUES(ps_filing),
|
||||
network_data_sources = VALUES(network_data_sources)`,
|
||||
[
|
||||
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.icp_filing, settings.ps_filing, settings.network_data_sources
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1130,7 +1116,21 @@ app.get('/api/metrics/network-history', async (req, res) => {
|
||||
if (cached) return res.json(cached);
|
||||
}
|
||||
|
||||
const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"');
|
||||
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 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) {
|
||||
return res.json({ timestamps: [], rx: [], tx: [] });
|
||||
}
|
||||
@@ -1234,12 +1234,6 @@ app.get('/api/metrics/server-history', requireServerDetailsAccess, async (req, r
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
// SPA fallback
|
||||
app.get('*', (req, res, next) => {
|
||||
if (req.path.startsWith('/api/') || req.path.includes('.')) return next();
|
||||
serveIndex(req, res);
|
||||
});
|
||||
|
||||
// ==================== Latency Routes CRUD ====================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user