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() {