修复数据库查询错误导致的故障
This commit is contained in:
@@ -621,7 +621,7 @@
|
|||||||
<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="btnSaveSecuritySettings">保存安全设置</button>
|
<button class="btn btn-add" id="btnSaveSecuritySettings">保存安全设置</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-message" id="siteSettingsMessage"></div>
|
<div class="form-message" id="securitySettingsMessage"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -642,6 +642,7 @@
|
|||||||
<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="btnSaveCustomMetrics">保存指标配置</button>
|
<button class="btn btn-add" id="btnSaveCustomMetrics">保存指标配置</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-message" id="customMetricsMessage"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
126
public/js/app.js
126
public/js/app.js
@@ -54,6 +54,8 @@
|
|||||||
btnSaveSiteSettings: document.getElementById('btnSaveSiteSettings'),
|
btnSaveSiteSettings: document.getElementById('btnSaveSiteSettings'),
|
||||||
btnSaveSecuritySettings: document.getElementById('btnSaveSecuritySettings'),
|
btnSaveSecuritySettings: document.getElementById('btnSaveSecuritySettings'),
|
||||||
siteSettingsMessage: document.getElementById('siteSettingsMessage'),
|
siteSettingsMessage: document.getElementById('siteSettingsMessage'),
|
||||||
|
securitySettingsMessage: document.getElementById('securitySettingsMessage'),
|
||||||
|
customMetricsMessage: document.getElementById('customMetricsMessage'),
|
||||||
logoText: document.getElementById('logoText'),
|
logoText: document.getElementById('logoText'),
|
||||||
logoIconContainer: document.getElementById('logoIconContainer'),
|
logoIconContainer: document.getElementById('logoIconContainer'),
|
||||||
defaultThemeInput: document.getElementById('defaultThemeInput'),
|
defaultThemeInput: document.getElementById('defaultThemeInput'),
|
||||||
@@ -121,8 +123,8 @@
|
|||||||
icpFilingInput: document.getElementById('icpFilingInput'),
|
icpFilingInput: document.getElementById('icpFilingInput'),
|
||||||
psFilingInput: document.getElementById('psFilingInput'),
|
psFilingInput: document.getElementById('psFilingInput'),
|
||||||
icpFilingDisplay: document.getElementById('icpFilingDisplay'),
|
icpFilingDisplay: document.getElementById('icpFilingDisplay'),
|
||||||
ps_filingDisplay: document.getElementById('psFilingDisplay'),
|
psFilingDisplay: document.getElementById('psFilingDisplay'),
|
||||||
ps_filingText: document.getElementById('psFilingText'),
|
psFilingText: document.getElementById('psFilingText'),
|
||||||
copyrightYear: document.getElementById('copyrightYear'),
|
copyrightYear: document.getElementById('copyrightYear'),
|
||||||
customMetricsList: document.getElementById('customMetricsList'),
|
customMetricsList: document.getElementById('customMetricsList'),
|
||||||
btnAddCustomMetric: document.getElementById('btnAddCustomMetric'),
|
btnAddCustomMetric: document.getElementById('btnAddCustomMetric'),
|
||||||
@@ -181,26 +183,7 @@
|
|||||||
throw lastError || new Error('All JSON sources failed');
|
throw lastError || new Error('All JSON sources failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Initialize ----
|
// ---- Custom Metrics Helpers ----
|
||||||
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 ----
|
|
||||||
|
|
||||||
function addMetricRow(config = {}) {
|
function addMetricRow(config = {}) {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'metric-row';
|
row.className = 'metric-row';
|
||||||
@@ -220,7 +203,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
row.querySelector('.btn-remove-metric').onclick = () => row.remove();
|
row.querySelector('.btn-remove-metric').onclick = () => row.remove();
|
||||||
dom.customMetricsList.appendChild(row);
|
dom.customMetricsList.appendChild(row);
|
||||||
}
|
}
|
||||||
@@ -231,12 +214,12 @@
|
|||||||
let list = [];
|
let list = [];
|
||||||
try {
|
try {
|
||||||
list = typeof metrics === 'string' ? JSON.parse(metrics) : (metrics || []);
|
list = typeof metrics === 'string' ? JSON.parse(metrics) : (metrics || []);
|
||||||
} catch(e) {}
|
} catch (e) { }
|
||||||
|
|
||||||
if (Array.isArray(list)) {
|
if (Array.isArray(list)) {
|
||||||
list.forEach(m => addMetricRow(m));
|
list.forEach(m => addMetricRow(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
// Add a placeholder/default row if empty
|
// Add a placeholder/default row if empty
|
||||||
}
|
}
|
||||||
@@ -257,11 +240,23 @@
|
|||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind Events
|
// ---- Initialize ----
|
||||||
if (dom.btnAddCustomMetric) dom.btnAddCustomMetric.onclick = () => addMetricRow();
|
function init() {
|
||||||
if (dom.btnSaveCustomMetrics) {
|
try {
|
||||||
dom.btnSaveCustomMetrics.onclick = saveSiteSettings;
|
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
|
// Initial map
|
||||||
initMap2D();
|
initMap2D();
|
||||||
@@ -319,8 +314,12 @@
|
|||||||
if (dom.btnSaveSiteSettings) {
|
if (dom.btnSaveSiteSettings) {
|
||||||
dom.btnSaveSiteSettings.addEventListener('click', saveSiteSettings);
|
dom.btnSaveSiteSettings.addEventListener('click', saveSiteSettings);
|
||||||
}
|
}
|
||||||
|
// Custom Metrics
|
||||||
|
if (dom.btnAddCustomMetric) {
|
||||||
|
dom.btnAddCustomMetric.addEventListener('click', () => addMetricRow());
|
||||||
|
}
|
||||||
if (dom.btnSaveCustomMetrics) {
|
if (dom.btnSaveCustomMetrics) {
|
||||||
dom.btnSaveCustomMetrics.onclick = saveSiteSettings;
|
dom.btnSaveCustomMetrics.addEventListener('click', saveSiteSettings);
|
||||||
}
|
}
|
||||||
if (dom.btnAddRoute) {
|
if (dom.btnAddRoute) {
|
||||||
dom.btnAddRoute.addEventListener('click', addLatencyRoute);
|
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) {
|
if (!user) {
|
||||||
showSiteMessage('Please login first', 'error');
|
showSiteMessage('请先登录后操作', 'error', messageTarget);
|
||||||
openLoginModal();
|
openLoginModal();
|
||||||
return;
|
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 = {
|
const settings = {
|
||||||
page_name: dom.pageNameInput.value.trim(),
|
page_name: dom.pageNameInput.value.trim(),
|
||||||
title: dom.siteTitleInput ? dom.siteTitleInput.value.trim() : dom.pageNameInput.value.trim(),
|
title: dom.siteTitleInput ? dom.siteTitleInput.value.trim() : dom.pageNameInput.value.trim(),
|
||||||
@@ -2230,14 +2242,6 @@
|
|||||||
custom_metrics: getCustomMetricsFromUI()
|
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 {
|
try {
|
||||||
const response = await fetch('/api/settings', {
|
const response = await fetch('/api/settings', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -2246,31 +2250,28 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showSiteMessage('设置保存成功', 'success');
|
const data = await response.json();
|
||||||
// Update global object and UI immediately
|
window.SITE_SETTINGS = data.settings;
|
||||||
window.SITE_SETTINGS = { ...window.SITE_SETTINGS, ...settings };
|
applySiteSettings(window.SITE_SETTINGS);
|
||||||
|
showSiteMessage('设置保存成功', 'success', messageTarget);
|
||||||
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
const savedTheme = localStorage.getItem('theme');
|
||||||
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
||||||
applyTheme(themeToApply);
|
applyTheme(themeToApply);
|
||||||
|
|
||||||
// Apply settings to UI (logo, name, etc.)
|
|
||||||
applySiteSettings(window.SITE_SETTINGS);
|
|
||||||
|
|
||||||
// Refresh overview and historical charts to reflect new source selections
|
// Refresh overview and historical charts to reflect new source selections
|
||||||
fetchNetworkHistory(true);
|
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')
|
fetch('/api/metrics/overview?force=true')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => updateDashboard(data))
|
.then(data => updateDashboard(data))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
const err = await response.json();
|
const err = await response.json();
|
||||||
showSiteMessage(`保存失败: ${err.error || '未知错误'}`, 'error');
|
showSiteMessage(`保存失败: ${err.error || '未知错误'}`, 'error', messageTarget);
|
||||||
if (response.status === 401) openLoginModal();
|
if (response.status === 401) openLoginModal();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showSiteMessage(`保存失败: ${err.message}`, 'error');
|
showSiteMessage(`请求失败: ${err.message}`, 'error', messageTarget);
|
||||||
console.error('Save settings error:', err);
|
console.error('Save settings error:', err);
|
||||||
} finally {
|
} finally {
|
||||||
saveButtons.forEach(btn => {
|
saveButtons.forEach(btn => {
|
||||||
@@ -2428,14 +2429,23 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function showSiteMessage(text, type) {
|
function showSiteMessage(text, type, target = null) {
|
||||||
dom.siteSettingsMessage.textContent = text;
|
const el = target || dom.siteSettingsMessage;
|
||||||
dom.siteSettingsMessage.className = `form-message ${type}`;
|
if (!el) return;
|
||||||
setTimeout(hideSiteMessage, 5000);
|
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() {
|
function hideSiteMessage(target = null) {
|
||||||
dom.siteSettingsMessage.className = 'form-message';
|
const el = target || dom.siteSettingsMessage;
|
||||||
|
if (el) el.className = 'form-message';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveChangePassword() {
|
async function saveChangePassword() {
|
||||||
|
|||||||
Reference in New Issue
Block a user