diff --git a/public/index.html b/public/index.html index 260f4f4..45ab952 100644 --- a/public/index.html +++ b/public/index.html @@ -621,7 +621,7 @@
-
+
@@ -642,6 +642,7 @@
+
diff --git a/public/js/app.js b/public/js/app.js index e84ae29..a5ee8bc 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -54,6 +54,8 @@ btnSaveSiteSettings: document.getElementById('btnSaveSiteSettings'), btnSaveSecuritySettings: document.getElementById('btnSaveSecuritySettings'), siteSettingsMessage: document.getElementById('siteSettingsMessage'), + securitySettingsMessage: document.getElementById('securitySettingsMessage'), + customMetricsMessage: document.getElementById('customMetricsMessage'), logoText: document.getElementById('logoText'), logoIconContainer: document.getElementById('logoIconContainer'), defaultThemeInput: document.getElementById('defaultThemeInput'), @@ -121,8 +123,8 @@ icpFilingInput: document.getElementById('icpFilingInput'), psFilingInput: document.getElementById('psFilingInput'), icpFilingDisplay: document.getElementById('icpFilingDisplay'), - ps_filingDisplay: document.getElementById('psFilingDisplay'), - ps_filingText: document.getElementById('psFilingText'), + psFilingDisplay: document.getElementById('psFilingDisplay'), + psFilingText: document.getElementById('psFilingText'), copyrightYear: document.getElementById('copyrightYear'), customMetricsList: document.getElementById('customMetricsList'), btnAddCustomMetric: document.getElementById('btnAddCustomMetric'), @@ -181,26 +183,7 @@ throw lastError || new Error('All JSON sources failed'); } - // ---- Initialize ---- - function init() { - try { - console.log('[Init] Start...'); - // Resource Gauges Time - updateGaugesTime(); - setInterval(updateGaugesTime, 1000); - - // Initial footer year - if (dom.copyrightYear) { - dom.copyrightYear.textContent = new Date().getFullYear(); - } - - // Initial theme check (localStorage handled after site settings load to ensure priority) - - // Network chart - networkChart = new AreaChart(dom.networkCanvas); - - // ---- Custom Metrics Helpers ---- - + // ---- Custom Metrics Helpers ---- function addMetricRow(config = {}) { const row = document.createElement('div'); row.className = 'metric-row'; @@ -220,7 +203,7 @@ `; - + row.querySelector('.btn-remove-metric').onclick = () => row.remove(); dom.customMetricsList.appendChild(row); } @@ -231,12 +214,12 @@ let list = []; try { list = typeof metrics === 'string' ? JSON.parse(metrics) : (metrics || []); - } catch(e) {} - + } catch (e) { } + if (Array.isArray(list)) { list.forEach(m => addMetricRow(m)); } - + if (list.length === 0) { // Add a placeholder/default row if empty } @@ -257,11 +240,23 @@ return metrics; } - // Bind Events - if (dom.btnAddCustomMetric) dom.btnAddCustomMetric.onclick = () => addMetricRow(); - if (dom.btnSaveCustomMetrics) { - dom.btnSaveCustomMetrics.onclick = saveSiteSettings; - } + // ---- Initialize ---- + function init() { + try { + console.log('[Init] Start...'); + // Resource Gauges Time + updateGaugesTime(); + setInterval(updateGaugesTime, 1000); + + // Initial footer year + if (dom.copyrightYear) { + dom.copyrightYear.textContent = new Date().getFullYear(); + } + + // Initial theme check (localStorage handled after site settings load to ensure priority) + + // Network chart + networkChart = new AreaChart(dom.networkCanvas); // Initial map initMap2D(); @@ -319,8 +314,12 @@ if (dom.btnSaveSiteSettings) { dom.btnSaveSiteSettings.addEventListener('click', saveSiteSettings); } + // Custom Metrics + if (dom.btnAddCustomMetric) { + dom.btnAddCustomMetric.addEventListener('click', () => addMetricRow()); + } if (dom.btnSaveCustomMetrics) { - dom.btnSaveCustomMetrics.onclick = saveSiteSettings; + dom.btnSaveCustomMetrics.addEventListener('click', saveSiteSettings); } if (dom.btnAddRoute) { dom.btnAddRoute.addEventListener('click', addLatencyRoute); @@ -2203,13 +2202,26 @@ } } - async function saveSiteSettings() { + async function saveSiteSettings(e) { + let messageTarget = dom.siteSettingsMessage; + if (e && e.target) { + if (e.target.id === 'btnSaveSecuritySettings') messageTarget = dom.securitySettingsMessage; + else if (e.target.id === 'btnSaveCustomMetrics') messageTarget = dom.customMetricsMessage; + } + if (!user) { - showSiteMessage('Please login first', 'error'); + showSiteMessage('请先登录后操作', 'error', messageTarget); openLoginModal(); return; } + const saveButtons = [dom.btnSaveSiteSettings, dom.btnSaveSecuritySettings, dom.btnSaveCustomMetrics].filter(b => b); + saveButtons.forEach(btn => { + btn.disabled = true; + btn.originalText = btn.textContent; + btn.textContent = '保存中...'; + }); + const settings = { page_name: dom.pageNameInput.value.trim(), title: dom.siteTitleInput ? dom.siteTitleInput.value.trim() : dom.pageNameInput.value.trim(), @@ -2230,14 +2242,6 @@ custom_metrics: getCustomMetricsFromUI() }; - // UI Feedback for both potential save buttons - const saveButtons = [dom.btnSaveSiteSettings, dom.btnSaveSecuritySettings].filter(b => b); - saveButtons.forEach(btn => { - btn.disabled = true; - btn.originalText = btn.textContent; - btn.textContent = '保存中...'; - }); - try { const response = await fetch('/api/settings', { method: 'POST', @@ -2246,31 +2250,28 @@ }); if (response.ok) { - showSiteMessage('设置保存成功', 'success'); - // Update global object and UI immediately - window.SITE_SETTINGS = { ...window.SITE_SETTINGS, ...settings }; + const data = await response.json(); + window.SITE_SETTINGS = data.settings; + applySiteSettings(window.SITE_SETTINGS); + showSiteMessage('设置保存成功', 'success', messageTarget); + const savedTheme = localStorage.getItem('theme'); const themeToApply = savedTheme || settings.default_theme || 'dark'; applyTheme(themeToApply); - - // Apply settings to UI (logo, name, etc.) - applySiteSettings(window.SITE_SETTINGS); // Refresh overview and historical charts to reflect new source selections fetchNetworkHistory(true); - // We can't force the WS broadcast easily from client, - // but we can fetch the overview via REST API once to update UI fetch('/api/metrics/overview?force=true') .then(res => res.json()) .then(data => updateDashboard(data)) .catch(() => {}); } else { const err = await response.json(); - showSiteMessage(`保存失败: ${err.error || '未知错误'}`, 'error'); + showSiteMessage(`保存失败: ${err.error || '未知错误'}`, 'error', messageTarget); if (response.status === 401) openLoginModal(); } } catch (err) { - showSiteMessage(`保存失败: ${err.message}`, 'error'); + showSiteMessage(`请求失败: ${err.message}`, 'error', messageTarget); console.error('Save settings error:', err); } finally { saveButtons.forEach(btn => { @@ -2428,14 +2429,23 @@ } }; - function showSiteMessage(text, type) { - dom.siteSettingsMessage.textContent = text; - dom.siteSettingsMessage.className = `form-message ${type}`; - setTimeout(hideSiteMessage, 5000); + function showSiteMessage(text, type, target = null) { + const el = target || dom.siteSettingsMessage; + if (!el) return; + el.textContent = text; + el.className = `form-message ${type}`; + + // Clear existing timeout if any (simplified) + if (el._msgTimeout) clearTimeout(el._msgTimeout); + el._msgTimeout = setTimeout(() => { + el.className = 'form-message'; + el._msgTimeout = null; + }, 5000); } - function hideSiteMessage() { - dom.siteSettingsMessage.className = 'form-message'; + function hideSiteMessage(target = null) { + const el = target || dom.siteSettingsMessage; + if (el) el.className = 'form-message'; } async function saveChangePassword() {