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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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 `<div class="dashboard-section-content"${isCollapsed ? ' style="display:none"' : ''}>${itemsHtml}</div>`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
<div class="metric-label">${t('device.metrics.errors')}</div>
|
||||
<div class="metric-value">${metrics.errors_count || 0}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-label">${t('device.metrics.uptime')}</div>
|
||||
<div class="metric-value">${formatUptime(metrics.uptime_seconds)}</div>
|
||||
</div>
|
||||
</div>
|
||||
${state.timing_total_ms != null ? `
|
||||
<div class="timing-breakdown">
|
||||
|
||||
@@ -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) {
|
||||
<div class="metric-label">${t('device.metrics.errors')}</div>
|
||||
<div class="metric-value">${metrics.errors_count || 0}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-label">${t('device.metrics.uptime')}</div>
|
||||
<div class="metric-value">${formatUptime(metrics.uptime_seconds)}</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Онлайн",
|
||||
|
||||
Reference in New Issue
Block a user