Fix critical frontend issues: race conditions, memory leaks, silent failures
- Add loading guard to loadPictureSources to prevent concurrent fetches - Pause perf chart polling and uptime timer when browser tab is hidden - Disconnect KC and LED preview WebSockets when leaving targets tab - Add error toasts to loadCaptureTemplates and saveKCBrightness - Skip auto-refresh polling when document is hidden - Widen auto-start dashboard cards for better text display Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -370,23 +370,32 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
|
||||
const autoStartTargets = enriched.filter(t => t.auto_start);
|
||||
if (autoStartTargets.length > 0) {
|
||||
const autoStartItems = autoStartTargets.map(target => {
|
||||
const autoStartCards = autoStartTargets.map(target => {
|
||||
const isRunning = !!(target.state && target.state.processing);
|
||||
const device = devicesMap[target.device_id];
|
||||
const deviceName = device ? device.name : '';
|
||||
const typeIcon = getTargetTypeIcon(target.target_type);
|
||||
const isLed = target.target_type !== 'key_colors';
|
||||
const typeLabel = isLed ? t('dashboard.type.led') : t('dashboard.type.kc');
|
||||
const subtitleParts = [typeLabel];
|
||||
if (isLed) {
|
||||
const device = target.device_id ? devicesMap[target.device_id] : null;
|
||||
if (device) subtitleParts.push((device.device_type || '').toUpperCase());
|
||||
const cssId = target.color_strip_source_id || '';
|
||||
if (cssId) {
|
||||
const css = cssSourceMap[cssId];
|
||||
if (css) subtitleParts.push(t(`color_strip.type.${css.source_type}`) || css.source_type);
|
||||
}
|
||||
}
|
||||
const statusBadge = isRunning
|
||||
? `<span class="dashboard-badge-active">${t('profiles.status.active')}</span>`
|
||||
: `<span class="dashboard-badge-stopped">${t('profiles.status.inactive')}</span>`;
|
||||
const subtitle = subtitleParts.length ? `<div class="dashboard-target-subtitle">${escapeHtml(subtitleParts.join(' · '))}</div>` : '';
|
||||
return `<div class="dashboard-target dashboard-autostart" data-target-id="${target.id}">
|
||||
<div class="dashboard-target-info">
|
||||
<span class="dashboard-target-icon">${ICON_AUTOSTART}</span>
|
||||
<div>
|
||||
<div class="dashboard-target-name">${escapeHtml(target.name)} ${statusBadge}</div>
|
||||
${deviceName ? `<div class="dashboard-target-subtitle">${typeIcon} ${escapeHtml(deviceName)}</div>` : `<div class="dashboard-target-subtitle">${typeIcon}</div>`}
|
||||
${subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-target-metrics"></div>
|
||||
<div class="dashboard-target-actions">
|
||||
<button class="btn btn-icon ${isRunning ? 'btn-warning' : 'btn-success'}" onclick="${isRunning ? `dashboardStopTarget('${target.id}')` : `dashboardStartTarget('${target.id}')`}" title="${isRunning ? t('device.stop') : t('device.start')}">
|
||||
${isRunning ? ICON_STOP_PLAIN : '▶'}
|
||||
@@ -394,6 +403,7 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
const autoStartItems = `<div class="dashboard-autostart-grid">${autoStartCards}</div>`;
|
||||
|
||||
dynamicHtml += `<div class="dashboard-section">
|
||||
${_sectionHeader('autostart', t('autostart.title'), autoStartTargets.length)}
|
||||
@@ -707,3 +717,13 @@ document.addEventListener('languageChanged', () => {
|
||||
if (perfEl) perfEl.remove();
|
||||
loadDashboard();
|
||||
});
|
||||
|
||||
// Pause uptime timer when browser tab is hidden, resume when visible
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
_stopUptimeTimer();
|
||||
} else if (_isDashboardActive() && _lastRunningIds.length > 0) {
|
||||
_cacheUptimeElements();
|
||||
_startUptimeTimer();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user