Batch API endpoints, reduce frontend polling by ~75%, fix resource leaks
Backend: add batch endpoints for target states, metrics, and device health to replace O(N) individual API calls per poll cycle. Frontend: use batch endpoints in dashboard/targets/profiles tabs, fix Chart.js instance leaks, debounce server event reloads, add i18n active-tab guards, clean up ResizeObserver on pattern editor close, cache uptime timer DOM refs, increase KC auto-refresh to 2s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ let _fpsCharts = {}; // { targetId: Chart }
|
||||
let _lastRunningIds = []; // sorted target IDs from previous render
|
||||
let _uptimeBase = {}; // { targetId: { seconds, timestamp } }
|
||||
let _uptimeTimer = null;
|
||||
let _uptimeElements = {}; // { targetId: HTMLElement } — cached DOM refs
|
||||
let _metricsElements = new Map();
|
||||
|
||||
function _loadFpsHistory() {
|
||||
@@ -50,11 +51,19 @@ function _getInterpolatedUptime(targetId) {
|
||||
return base.seconds + elapsed;
|
||||
}
|
||||
|
||||
function _cacheUptimeElements() {
|
||||
_uptimeElements = {};
|
||||
for (const id of _lastRunningIds) {
|
||||
const el = document.querySelector(`[data-uptime-text="${id}"]`);
|
||||
if (el) _uptimeElements[id] = el;
|
||||
}
|
||||
}
|
||||
|
||||
function _startUptimeTimer() {
|
||||
if (_uptimeTimer) return;
|
||||
_uptimeTimer = setInterval(() => {
|
||||
for (const id of _lastRunningIds) {
|
||||
const el = document.querySelector(`[data-uptime-text="${id}"]`);
|
||||
const el = _uptimeElements[id];
|
||||
if (!el) continue;
|
||||
const seconds = _getInterpolatedUptime(id);
|
||||
if (seconds != null) {
|
||||
@@ -70,6 +79,7 @@ function _stopUptimeTimer() {
|
||||
_uptimeTimer = null;
|
||||
}
|
||||
_uptimeBase = {};
|
||||
_uptimeElements = {};
|
||||
}
|
||||
|
||||
function _destroyFpsCharts() {
|
||||
@@ -320,18 +330,16 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
if (targets.length === 0 && profiles.length === 0) {
|
||||
dynamicHtml = `<div class="dashboard-no-targets">${t('dashboard.no_targets')}</div>`;
|
||||
} else {
|
||||
const enriched = await Promise.all(targets.map(async (target) => {
|
||||
try {
|
||||
const [stateResp, metricsResp] = await Promise.all([
|
||||
fetch(`${API_BASE}/picture-targets/${target.id}/state`, { headers: getHeaders() }),
|
||||
fetch(`${API_BASE}/picture-targets/${target.id}/metrics`, { headers: getHeaders() }),
|
||||
]);
|
||||
const state = stateResp.ok ? await stateResp.json() : {};
|
||||
const metrics = metricsResp.ok ? await metricsResp.json() : {};
|
||||
return { ...target, state, metrics };
|
||||
} catch {
|
||||
return target;
|
||||
}
|
||||
const [batchStatesResp, batchMetricsResp] = await Promise.all([
|
||||
fetchWithAuth('/picture-targets/batch/states'),
|
||||
fetchWithAuth('/picture-targets/batch/metrics'),
|
||||
]);
|
||||
const allStates = batchStatesResp.ok ? (await batchStatesResp.json()).states : {};
|
||||
const allMetrics = batchMetricsResp.ok ? (await batchMetricsResp.json()).metrics : {};
|
||||
const enriched = targets.map(target => ({
|
||||
...target,
|
||||
state: allStates[target.id] || {},
|
||||
metrics: allMetrics[target.id] || {},
|
||||
}));
|
||||
|
||||
const running = enriched.filter(t => t.state && t.state.processing);
|
||||
@@ -412,6 +420,7 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
}
|
||||
}
|
||||
_lastRunningIds = runningIds;
|
||||
_cacheUptimeElements();
|
||||
_initFpsCharts(runningIds);
|
||||
_startUptimeTimer();
|
||||
startPerfPolling();
|
||||
@@ -637,13 +646,15 @@ function _isDashboardActive() {
|
||||
return (localStorage.getItem('activeTab') || 'dashboard') === 'dashboard';
|
||||
}
|
||||
|
||||
document.addEventListener('server:state_change', () => {
|
||||
if (_isDashboardActive()) loadDashboard();
|
||||
});
|
||||
let _eventDebounceTimer = null;
|
||||
function _debouncedDashboardReload(forceFullRender = false) {
|
||||
if (!_isDashboardActive()) return;
|
||||
clearTimeout(_eventDebounceTimer);
|
||||
_eventDebounceTimer = setTimeout(() => loadDashboard(forceFullRender), 300);
|
||||
}
|
||||
|
||||
document.addEventListener('server:profile_state_changed', () => {
|
||||
if (_isDashboardActive()) loadDashboard(true);
|
||||
});
|
||||
document.addEventListener('server:state_change', () => _debouncedDashboardReload());
|
||||
document.addEventListener('server:profile_state_changed', () => _debouncedDashboardReload(true));
|
||||
|
||||
// Re-render dashboard when language changes
|
||||
document.addEventListener('languageChanged', () => {
|
||||
|
||||
Reference in New Issue
Block a user