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": "Онлайн",