From 48651f0a4e4174eb2b890606f0183bd5201f3f78 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Tue, 24 Feb 2026 14:36:14 +0300 Subject: [PATCH] Show uptime in target cards, fix dashboard uptime stale after tab switch Add uptime metric to both LED and KC target cards in the targets tab. Move formatUptime() to shared ui.js module. Fix dashboard uptime freezing when switching tabs by re-caching DOM element refs on early return paths. Co-Authored-By: Claude Opus 4.6 --- .../src/wled_controller/static/js/core/ui.js | 10 ++++++++++ .../static/js/features/dashboard.js | 18 +++++++----------- .../static/js/features/kc-targets.js | 6 +++++- .../static/js/features/targets.js | 6 +++++- .../src/wled_controller/static/locales/en.json | 1 + .../src/wled_controller/static/locales/ru.json | 1 + 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/server/src/wled_controller/static/js/core/ui.js b/server/src/wled_controller/static/js/core/ui.js index 4b8b58e..b24e0a7 100644 --- a/server/src/wled_controller/static/js/core/ui.js +++ b/server/src/wled_controller/static/js/core/ui.js @@ -292,3 +292,13 @@ export function hideOverlaySpinner() { const overlay = document.getElementById('overlay-spinner'); if (overlay) overlay.remove(); } + +export function formatUptime(seconds) { + if (!seconds || seconds <= 0) return '-'; + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + if (h > 0) return t('time.hours_minutes', { h, m }); + if (m > 0) return t('time.minutes_seconds', { m, s }); + return t('time.seconds', { s }); +} diff --git a/server/src/wled_controller/static/js/features/dashboard.js b/server/src/wled_controller/static/js/features/dashboard.js index 286f1a1..224bd1d 100644 --- a/server/src/wled_controller/static/js/features/dashboard.js +++ b/server/src/wled_controller/static/js/features/dashboard.js @@ -5,7 +5,7 @@ import { apiKey, _dashboardLoading, set_dashboardLoading, dashboardPollInterval, setDashboardPollInterval } from '../core/state.js'; import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js'; import { t } from '../core/i18n.js'; -import { showToast } from '../core/ui.js'; +import { showToast, formatUptime } from '../core/ui.js'; import { renderPerfSection, initPerfCharts, startPerfPolling, stopPerfPolling } from './perf-charts.js'; import { startAutoRefresh } from './tabs.js'; @@ -292,16 +292,6 @@ function _sectionContent(sectionKey, itemsHtml) { return ``; } -function formatUptime(seconds) { - if (!seconds || seconds <= 0) return '-'; - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const s = Math.floor(seconds % 60); - if (h > 0) return t('time.hours_minutes', { h, m }); - if (m > 0) return t('time.minutes_seconds', { m, s }); - return t('time.seconds', { s }); -} - export async function loadDashboard(forceFullRender = false) { if (_dashboardLoading) return; set_dashboardLoading(true); @@ -356,12 +346,18 @@ export async function loadDashboard(forceFullRender = false) { const structureUnchanged = hasExistingDom && newRunningIds === prevRunningIds; if (structureUnchanged && !forceFullRender && running.length > 0) { _updateRunningMetrics(running); + _cacheUptimeElements(); + _startUptimeTimer(); + startPerfPolling(); set_dashboardLoading(false); return; } if (structureUnchanged && forceFullRender) { if (running.length > 0) _updateRunningMetrics(running); _updateProfilesInPlace(profiles); + _cacheUptimeElements(); + _startUptimeTimer(); + startPerfPolling(); set_dashboardLoading(false); return; } diff --git a/server/src/wled_controller/static/js/features/kc-targets.js b/server/src/wled_controller/static/js/features/kc-targets.js index 1fd9b8d..97dac04 100644 --- a/server/src/wled_controller/static/js/features/kc-targets.js +++ b/server/src/wled_controller/static/js/features/kc-targets.js @@ -12,7 +12,7 @@ import { } from '../core/state.js'; import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js'; import { t } from '../core/i18n.js'; -import { lockBody, showToast, showConfirm } from '../core/ui.js'; +import { lockBody, showToast, showConfirm, formatUptime } from '../core/ui.js'; import { Modal } from '../core/modal.js'; class KCEditorModal extends Modal { @@ -116,6 +116,10 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap) {
${t('device.metrics.errors')}
${metrics.errors_count || 0}
+
+
${t('device.metrics.uptime')}
+
${formatUptime(metrics.uptime_seconds)}
+
${state.timing_total_ms != null ? `
diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index e5d33f0..efe1aa3 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -11,7 +11,7 @@ import { } from '../core/state.js'; import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js'; import { t } from '../core/i18n.js'; -import { showToast, showConfirm } from '../core/ui.js'; +import { showToast, showConfirm, formatUptime } from '../core/ui.js'; import { Modal } from '../core/modal.js'; import { createDeviceCard, attachDeviceListeners, fetchDeviceBrightness, _computeMaxFps } from './devices.js'; import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from './kc-targets.js'; @@ -696,6 +696,10 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap) {
${t('device.metrics.errors')}
${metrics.errors_count || 0}
+
+
${t('device.metrics.uptime')}
+
${formatUptime(metrics.uptime_seconds)}
+
` : ''} diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json index 0595eb6..b704aa7 100644 --- a/server/src/wled_controller/static/locales/en.json +++ b/server/src/wled_controller/static/locales/en.json @@ -160,6 +160,7 @@ "device.metrics.frames_skipped": "Skipped", "device.metrics.keepalive": "Keepalive", "device.metrics.errors": "Errors", + "device.metrics.uptime": "Uptime", "device.metrics.timing": "Pipeline timing:", "device.metrics.device_fps": "Device refresh rate", "device.health.online": "Online", diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json index 10bd15c..12b44c2 100644 --- a/server/src/wled_controller/static/locales/ru.json +++ b/server/src/wled_controller/static/locales/ru.json @@ -160,6 +160,7 @@ "device.metrics.frames_skipped": "Пропущено", "device.metrics.keepalive": "Keepalive", "device.metrics.errors": "Ошибки", + "device.metrics.uptime": "Время работы", "device.metrics.timing": "Тайминг пайплайна:", "device.metrics.device_fps": "Частота обновления устройства", "device.health.online": "Онлайн",