diff --git a/public/index.html b/public/index.html index 9436e2d..5872f39 100644 --- a/public/index.html +++ b/public/index.html @@ -507,6 +507,15 @@ +
+ + +

开启后,未登录访客仍可看到大屏总览,但点击单台服务器时需要先登录。

+
diff --git a/public/js/app.js b/public/js/app.js index 04dfefe..161b2a9 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -44,6 +44,7 @@ faviconUrlInput: document.getElementById('faviconUrlInput'), logoUrlDarkInput: document.getElementById('logoUrlDarkInput'), showPageNameInput: document.getElementById('showPageNameInput'), + requireLoginForServerDetailsInput: document.getElementById('requireLoginForServerDetailsInput'), // Site Settings modalTabs: document.querySelectorAll('.modal-tab'), tabContents: document.querySelectorAll('.tab-content'), @@ -417,6 +418,10 @@ const job = row.getAttribute('data-job'); const source = row.getAttribute('data-source'); if (instance && job && source) { + if (requiresLoginForServerDetails() && !user) { + promptLogin('登录后可查看服务器详细指标'); + return; + } openServerDetail(instance, job, source); } } @@ -528,6 +533,7 @@ if (dom.defaultThemeInput) dom.defaultThemeInput.value = window.SITE_SETTINGS.default_theme || 'dark'; if (dom.show95BandwidthInput) dom.show95BandwidthInput.value = window.SITE_SETTINGS.show_95_bandwidth ? "1" : "0"; if (dom.p95TypeSelect) dom.p95TypeSelect.value = window.SITE_SETTINGS.p95_type || 'tx'; + if (dom.requireLoginForServerDetailsInput) dom.requireLoginForServerDetailsInput.value = window.SITE_SETTINGS.require_login_for_server_details ? "1" : "0"; if (dom.icpFilingInput) dom.icpFilingInput.value = window.SITE_SETTINGS.icp_filing || ''; if (dom.psFilingInput) dom.psFilingInput.value = window.SITE_SETTINGS.ps_filing || ''; if (dom.logoUrlDarkInput) dom.logoUrlDarkInput.value = window.SITE_SETTINGS.logo_url_dark || ''; @@ -692,6 +698,11 @@ return null; } + function requiresLoginForServerDetails() { + if (!window.SITE_SETTINGS) return true; + return !!window.SITE_SETTINGS.require_login_for_server_details; + } + function renderLogoImage(url) { if (!dom.logoIconContainer) return; const safeUrl = sanitizeAssetUrl(url); @@ -1895,6 +1906,7 @@ if (dom.logoUrlDarkInput) dom.logoUrlDarkInput.value = settings.logo_url_dark || ''; if (dom.faviconUrlInput) dom.faviconUrlInput.value = settings.favicon_url || ''; 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 Theme Priority: localStorage > Site Default const savedTheme = localStorage.getItem('theme'); @@ -2016,6 +2028,7 @@ logo_url_dark: dom.logoUrlDarkInput ? dom.logoUrlDarkInput.value.trim() : '', favicon_url: dom.faviconUrlInput ? dom.faviconUrlInput.value.trim() : '', show_page_name: dom.showPageNameInput ? parseInt(dom.showPageNameInput.value) : 1, + require_login_for_server_details: dom.requireLoginForServerDetailsInput ? (dom.requireLoginForServerDetailsInput.value === "1") : true, default_theme: dom.defaultThemeInput ? dom.defaultThemeInput.value : 'dark', show_95_bandwidth: dom.show95BandwidthInput ? (dom.show95BandwidthInput.value === "1") : false, p95_type: dom.p95TypeSelect ? dom.p95TypeSelect.value : 'tx', diff --git a/server/db-integrity-check.js b/server/db-integrity-check.js index 117ce47..8006f17 100644 --- a/server/db-integrity-check.js +++ b/server/db-integrity-check.js @@ -83,6 +83,7 @@ async function checkAndFixDatabase() { await addColumn('show_page_name', "ALTER TABLE site_settings ADD COLUMN show_page_name TINYINT(1) DEFAULT 1 AFTER page_name"); await addColumn('show_95_bandwidth', "ALTER TABLE site_settings ADD COLUMN show_95_bandwidth TINYINT(1) DEFAULT 0 AFTER default_theme"); await addColumn('p95_type', "ALTER TABLE site_settings ADD COLUMN p95_type VARCHAR(20) DEFAULT 'tx' AFTER show_95_bandwidth"); + await addColumn('require_login_for_server_details', "ALTER TABLE site_settings ADD COLUMN require_login_for_server_details TINYINT(1) DEFAULT 1 AFTER p95_type"); await addColumn('blackbox_source_id', "ALTER TABLE site_settings ADD COLUMN blackbox_source_id INT AFTER p95_type"); await addColumn('latency_source', "ALTER TABLE site_settings ADD COLUMN latency_source VARCHAR(100) AFTER blackbox_source_id"); await addColumn('latency_dest', "ALTER TABLE site_settings ADD COLUMN latency_dest VARCHAR(100) AFTER latency_source"); @@ -135,6 +136,7 @@ async function createTable(tableName) { default_theme VARCHAR(20) DEFAULT 'dark', show_95_bandwidth TINYINT(1) DEFAULT 0, p95_type VARCHAR(20) DEFAULT 'tx', + require_login_for_server_details TINYINT(1) DEFAULT 1, blackbox_source_id INT, latency_source VARCHAR(100), latency_dest VARCHAR(100), diff --git a/server/index.js b/server/index.js index ddf4151..39c0f24 100644 --- a/server/index.js +++ b/server/index.js @@ -140,11 +140,37 @@ function getPublicSiteSettings(settings = {}) { default_theme: settings.default_theme || 'dark', show_95_bandwidth: settings.show_95_bandwidth ? 1 : 0, p95_type: settings.p95_type || 'tx', + require_login_for_server_details: settings.require_login_for_server_details !== undefined + ? (settings.require_login_for_server_details ? 1 : 0) + : 1, icp_filing: settings.icp_filing || null, ps_filing: settings.ps_filing || null }; } +async function getSiteSettingsRow() { + const [rows] = await db.query('SELECT * FROM site_settings WHERE id = 1'); + return rows.length > 0 ? rows[0] : {}; +} + +async function requireServerDetailsAccess(req, res, next) { + try { + const settings = await getSiteSettingsRow(); + const requiresLogin = settings.require_login_for_server_details !== undefined + ? !!settings.require_login_for_server_details + : true; + + if (!requiresLogin) { + return next(); + } + + return requireAuth(req, res, next); + } catch (err) { + console.error('Server details access check failed:', err); + return res.status(500).json({ error: 'Failed to verify detail access' }); + } +} + function getCookieOptions(req, maxAgeSeconds) { const options = ['Path=/', 'HttpOnly', 'SameSite=Strict']; if (typeof maxAgeSeconds === 'number') { @@ -508,11 +534,12 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => { title VARCHAR(255) DEFAULT '数据可视化展示大屏', logo_url TEXT, logo_url_dark TEXT, - favicon_url TEXT, - default_theme VARCHAR(20) DEFAULT 'dark', - show_95_bandwidth TINYINT(1) DEFAULT 0, - p95_type VARCHAR(20) DEFAULT 'tx', - blackbox_source_id INT, + favicon_url TEXT, + default_theme VARCHAR(20) DEFAULT 'dark', + show_95_bandwidth TINYINT(1) DEFAULT 0, + p95_type VARCHAR(20) DEFAULT 'tx', + require_login_for_server_details TINYINT(1) DEFAULT 1, + blackbox_source_id INT, latency_source VARCHAR(100), latency_dest VARCHAR(100), latency_target VARCHAR(255), @@ -530,6 +557,7 @@ app.post('/api/setup/init', ensureSetupAccess, async (req, res) => { await connection.query("ALTER TABLE prometheus_sources ADD COLUMN IF NOT EXISTS is_server_source TINYINT(1) DEFAULT 1 AFTER description"); 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(` CREATE TABLE IF NOT EXISTS latency_routes ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -878,11 +906,11 @@ app.post('/api/settings', requireAuth, async (req, res) => { let current = rows.length > 0 ? rows[0] : {}; // 2. Destructure fields from body - const { - page_name, show_page_name, title, logo_url, logo_url_dark, favicon_url, - default_theme, show_95_bandwidth, p95_type, - icp_filing, ps_filing - } = req.body; + 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 + } = req.body; // 3. Prepare parameters, prioritizing body but falling back to current const settings = { @@ -891,13 +919,16 @@ app.post('/api/settings', requireAuth, async (req, res) => { title: title !== undefined ? title : (current.title || '数据可视化展示大屏'), logo_url: logo_url !== undefined ? logo_url : (current.logo_url || null), logo_url_dark: logo_url_dark !== undefined ? logo_url_dark : (current.logo_url_dark || null), - favicon_url: favicon_url !== undefined ? favicon_url : (current.favicon_url || null), - default_theme: default_theme !== undefined ? default_theme : (current.default_theme || 'dark'), - show_95_bandwidth: show_95_bandwidth !== undefined ? (show_95_bandwidth ? 1 : 0) : (current.show_95_bandwidth || 0), - p95_type: p95_type !== undefined ? p95_type : (current.p95_type || 'tx'), - 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 + favicon_url: favicon_url !== undefined ? favicon_url : (current.favicon_url || null), + default_theme: default_theme !== undefined ? default_theme : (current.default_theme || 'dark'), + show_95_bandwidth: show_95_bandwidth !== undefined ? (show_95_bandwidth ? 1 : 0) : (current.show_95_bandwidth || 0), + p95_type: p95_type !== undefined ? p95_type : (current.p95_type || 'tx'), + 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 icp_filing: icp_filing !== undefined ? icp_filing : (current.icp_filing || null), ps_filing: ps_filing !== undefined ? ps_filing : (current.ps_filing || null) @@ -905,34 +936,35 @@ app.post('/api/settings', requireAuth, async (req, res) => { // 4. Update database 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, - blackbox_source_id, latency_source, latency_dest, latency_target, - icp_filing, ps_filing - ) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - page_name = VALUES(page_name), - show_page_name = VALUES(show_page_name), - title = VALUES(title), - logo_url = VALUES(logo_url), + `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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + page_name = VALUES(page_name), + show_page_name = VALUES(show_page_name), + title = VALUES(title), + logo_url = VALUES(logo_url), logo_url_dark = VALUES(logo_url_dark), - favicon_url = VALUES(favicon_url), - default_theme = VALUES(default_theme), - show_95_bandwidth = VALUES(show_95_bandwidth), - p95_type = VALUES(p95_type), - blackbox_source_id = VALUES(blackbox_source_id), - latency_source = VALUES(latency_source), - latency_dest = VALUES(latency_dest), - latency_target = VALUES(latency_target), - icp_filing = VALUES(icp_filing), - ps_filing = VALUES(ps_filing)`, - [ - 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.blackbox_source_id, settings.latency_source, settings.latency_dest, settings.latency_target, - settings.icp_filing, settings.ps_filing - ] + favicon_url = VALUES(favicon_url), + default_theme = VALUES(default_theme), + show_95_bandwidth = VALUES(show_95_bandwidth), + p95_type = VALUES(p95_type), + require_login_for_server_details = VALUES(require_login_for_server_details), + blackbox_source_id = VALUES(blackbox_source_id), + latency_source = VALUES(latency_source), + latency_dest = VALUES(latency_dest), + latency_target = VALUES(latency_target), + icp_filing = VALUES(icp_filing), + ps_filing = VALUES(ps_filing)`, + [ + 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 + ] ); res.json({ success: true }); @@ -1153,7 +1185,7 @@ app.get('/api/metrics/cpu-history', async (req, res) => { }); // Get detailed metrics for a specific server -app.get('/api/metrics/server-details', requireAuth, async (req, res) => { +app.get('/api/metrics/server-details', requireServerDetailsAccess, async (req, res) => { const { instance, job, source } = req.query; if (!instance || !job || !source) { @@ -1178,7 +1210,7 @@ app.get('/api/metrics/server-details', requireAuth, async (req, res) => { }); // Get historical metrics for a specific server -app.get('/api/metrics/server-history', requireAuth, async (req, res) => { +app.get('/api/metrics/server-history', requireServerDetailsAccess, async (req, res) => { const { instance, job, source, metric, range, start, end } = req.query; if (!instance || !job || !source || !metric) {