优化内存泄露的问题
This commit is contained in:
@@ -111,6 +111,8 @@
|
|||||||
// ---- State ----
|
// ---- State ----
|
||||||
let previousMetrics = null;
|
let previousMetrics = null;
|
||||||
let networkChart = null;
|
let networkChart = null;
|
||||||
|
let ws = null; // WebSocket instance
|
||||||
|
let isWsConnecting = false; // Connection lock
|
||||||
let user = null; // Currently logged in user
|
let user = null; // Currently logged in user
|
||||||
let currentServerDetail = { instance: null, job: null, source: null, charts: {} };
|
let currentServerDetail = { instance: null, job: null, source: null, charts: {} };
|
||||||
let allServersData = [];
|
let allServersData = [];
|
||||||
@@ -119,6 +121,10 @@
|
|||||||
let pageSize = 20;
|
let pageSize = 20;
|
||||||
let currentLatencies = []; // Array of {id, source, dest, latency}
|
let currentLatencies = []; // Array of {id, source, dest, latency}
|
||||||
let latencyTimer = null;
|
let latencyTimer = null;
|
||||||
|
let mapResizeHandler = null; // For map cleanup
|
||||||
|
let siteThemeQuery = null; // For media query cleanup
|
||||||
|
let siteThemeHandler = null;
|
||||||
|
let backgroundIntervals = []; // To track setIntervals
|
||||||
|
|
||||||
// Load sort state from localStorage or use default
|
// Load sort state from localStorage or use default
|
||||||
let currentSort = { column: 'up', direction: 'desc' };
|
let currentSort = { column: 'up', direction: 'desc' };
|
||||||
@@ -357,28 +363,42 @@
|
|||||||
|
|
||||||
// Still populate inputs
|
// Still populate inputs
|
||||||
dom.pageNameInput.value = window.SITE_SETTINGS.page_name || '';
|
dom.pageNameInput.value = window.SITE_SETTINGS.page_name || '';
|
||||||
dom.siteTitleInput.value = window.SITE_SETTINGS.title || '';
|
if (dom.siteTitleInput) dom.siteTitleInput.value = window.SITE_SETTINGS.title || '';
|
||||||
dom.logoUrlInput.value = window.SITE_SETTINGS.logo_url || '';
|
if (dom.logoUrlInput) dom.logoUrlInput.value = window.SITE_SETTINGS.logo_url || '';
|
||||||
dom.defaultThemeInput.value = window.SITE_SETTINGS.default_theme || 'dark';
|
if (dom.defaultThemeInput) dom.defaultThemeInput.value = window.SITE_SETTINGS.default_theme || 'dark';
|
||||||
dom.show95BandwidthInput.value = window.SITE_SETTINGS.show_95_bandwidth ? "1" : "0";
|
if (dom.show95BandwidthInput) dom.show95BandwidthInput.value = window.SITE_SETTINGS.show_95_bandwidth ? "1" : "0";
|
||||||
dom.p95TypeSelect.value = window.SITE_SETTINGS.p95_type || 'tx';
|
if (dom.p95TypeSelect) dom.p95TypeSelect.value = window.SITE_SETTINGS.p95_type || 'tx';
|
||||||
// Latency routes loaded separately in openSettings or on startup
|
// Latency routes loaded separately in openSettings or on startup
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSiteSettings();
|
loadSiteSettings();
|
||||||
|
|
||||||
// setInterval(fetchMetrics, REFRESH_INTERVAL); - Now using WebSockets
|
// Track intervals for resource management
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
setInterval(fetchNetworkHistory, NETWORK_HISTORY_INTERVAL);
|
backgroundIntervals.push(setInterval(fetchNetworkHistory, NETWORK_HISTORY_INTERVAL));
|
||||||
fetchLatency();
|
backgroundIntervals.push(setInterval(fetchLatency, REFRESH_INTERVAL));
|
||||||
setInterval(fetchLatency, REFRESH_INTERVAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Real-time WebSocket ----
|
// ---- Real-time WebSocket ----
|
||||||
function initWebSocket() {
|
function initWebSocket() {
|
||||||
|
if (isWsConnecting) return;
|
||||||
|
isWsConnecting = true;
|
||||||
|
|
||||||
|
if (ws) {
|
||||||
|
ws.onmessage = null;
|
||||||
|
ws.onclose = null;
|
||||||
|
ws.onerror = null;
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsUrl = `${protocol}//${window.location.host}`;
|
const wsUrl = `${protocol}//${window.location.host}`;
|
||||||
const ws = new WebSocket(wsUrl);
|
ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
isWsConnecting = false;
|
||||||
|
console.log('WS connection established');
|
||||||
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
@@ -394,12 +414,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
|
isWsConnecting = false;
|
||||||
console.log('WS connection closed. Reconnecting in 5s...');
|
console.log('WS connection closed. Reconnecting in 5s...');
|
||||||
setTimeout(initWebSocket, 5000);
|
setTimeout(initWebSocket, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = (err) => {
|
ws.onerror = (err) => {
|
||||||
// Small silent error here to not alert the user constantly if server is down during maintenance
|
isWsConnecting = false;
|
||||||
ws.close();
|
ws.close();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -597,6 +618,13 @@
|
|||||||
|
|
||||||
echarts.registerMap('world', worldJSON);
|
echarts.registerMap('world', worldJSON);
|
||||||
|
|
||||||
|
if (myMap2D) {
|
||||||
|
myMap2D.dispose();
|
||||||
|
if (mapResizeHandler) {
|
||||||
|
window.removeEventListener('resize', mapResizeHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
myMap2D = echarts.init(dom.globeContainer);
|
myMap2D = echarts.init(dom.globeContainer);
|
||||||
|
|
||||||
const isLight = document.documentElement.classList.contains('light-theme');
|
const isLight = document.documentElement.classList.contains('light-theme');
|
||||||
@@ -663,7 +691,10 @@
|
|||||||
|
|
||||||
myMap2D.setOption(option);
|
myMap2D.setOption(option);
|
||||||
|
|
||||||
window.addEventListener('resize', () => myMap2D.resize());
|
mapResizeHandler = debounce(() => {
|
||||||
|
if (myMap2D) myMap2D.resize();
|
||||||
|
}, 100);
|
||||||
|
window.addEventListener('resize', mapResizeHandler);
|
||||||
|
|
||||||
if (allServersData.length > 0) {
|
if (allServersData.length > 0) {
|
||||||
updateMap2D(allServersData);
|
updateMap2D(allServersData);
|
||||||
@@ -1155,6 +1186,13 @@
|
|||||||
|
|
||||||
// ---- Server Detail ----
|
// ---- Server Detail ----
|
||||||
async function openServerDetail(instance, job, source) {
|
async function openServerDetail(instance, job, source) {
|
||||||
|
// Cleanup old charts if any were still present from a previous open (safety)
|
||||||
|
if (currentServerDetail.charts) {
|
||||||
|
Object.values(currentServerDetail.charts).forEach(chart => {
|
||||||
|
if (chart && chart.destroy) chart.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
currentServerDetail = { instance, job, source, charts: {} };
|
currentServerDetail = { instance, job, source, charts: {} };
|
||||||
dom.serverDetailTitle.textContent = `${job}`;
|
dom.serverDetailTitle.textContent = `${job}`;
|
||||||
dom.serverDetailModal.classList.add('active');
|
dom.serverDetailModal.classList.add('active');
|
||||||
@@ -1505,15 +1543,21 @@
|
|||||||
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
const themeToApply = savedTheme || settings.default_theme || 'dark';
|
||||||
applyTheme(themeToApply);
|
applyTheme(themeToApply);
|
||||||
|
|
||||||
// Listen for system theme changes if set to auto
|
// Listen for system theme changes if set to auto (cleanup existing listener first)
|
||||||
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', () => {
|
if (siteThemeQuery && siteThemeHandler) {
|
||||||
|
siteThemeQuery.removeEventListener('change', siteThemeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
siteThemeQuery = window.matchMedia('(prefers-color-scheme: light)');
|
||||||
|
siteThemeHandler = () => {
|
||||||
const currentSavedTheme = localStorage.getItem('theme');
|
const currentSavedTheme = localStorage.getItem('theme');
|
||||||
const defaultTheme = window.SITE_SETTINGS ? window.SITE_SETTINGS.default_theme : 'dark';
|
const defaultTheme = window.SITE_SETTINGS ? window.SITE_SETTINGS.default_theme : 'dark';
|
||||||
const activeTheme = currentSavedTheme || defaultTheme;
|
const activeTheme = currentSavedTheme || defaultTheme;
|
||||||
if (activeTheme === 'auto') {
|
if (activeTheme === 'auto') {
|
||||||
applyTheme('auto');
|
applyTheme('auto');
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
siteThemeQuery.addEventListener('change', siteThemeHandler);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading site settings:', err);
|
console.error('Error loading site settings:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ class AreaChart {
|
|||||||
this.dpr = window.devicePixelRatio || 1;
|
this.dpr = window.devicePixelRatio || 1;
|
||||||
this.padding = { top: 20, right: 16, bottom: 32, left: 56 };
|
this.padding = { top: 20, right: 16, bottom: 32, left: 56 };
|
||||||
|
|
||||||
this.currentMaxVal = 0;
|
|
||||||
this.prevMaxVal = 0;
|
this.prevMaxVal = 0;
|
||||||
|
this.currentMaxVal = 0;
|
||||||
|
|
||||||
this._resize = this.resize.bind(this);
|
// Use debounced resize for performance and safety
|
||||||
|
this._resize = typeof debounce === 'function' ? debounce(this.resize.bind(this), 100) : this.resize.bind(this);
|
||||||
window.addEventListener('resize', this._resize);
|
window.addEventListener('resize', this._resize);
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
@@ -335,7 +336,8 @@ class MetricChart {
|
|||||||
this.prevMaxVal = 0;
|
this.prevMaxVal = 0;
|
||||||
this.currentMaxVal = 0;
|
this.currentMaxVal = 0;
|
||||||
|
|
||||||
this._resize = this.resize.bind(this);
|
// Use debounced resize for performance and safety
|
||||||
|
this._resize = typeof debounce === 'function' ? debounce(this.resize.bind(this), 100) : this.resize.bind(this);
|
||||||
window.addEventListener('resize', this._resize);
|
window.addEventListener('resize', this._resize);
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,3 +111,17 @@ function animateValue(element, start, end, duration = 600) {
|
|||||||
|
|
||||||
requestAnimationFrame(update);
|
requestAnimationFrame(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce function to limit execution frequency
|
||||||
|
*/
|
||||||
|
function debounce(fn, delay) {
|
||||||
|
let timer = null;
|
||||||
|
return function (...args) {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
fn.apply(this, args);
|
||||||
|
timer = null;
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user