diff --git a/server/src/wled_controller/static/app.js b/server/src/wled_controller/static/app.js index 473b8d4..76c19cc 100644 --- a/server/src/wled_controller/static/app.js +++ b/server/src/wled_controller/static/app.js @@ -3,6 +3,7 @@ let refreshInterval = null; let apiKey = null; let kcTestAutoRefresh = null; // interval ID for KC test auto-refresh let kcTestTargetId = null; // currently testing KC target +let _dashboardWS = null; // WebSocket for dashboard live updates // Toggle hint description visibility next to a label function toggleHint(btn) { @@ -588,10 +589,16 @@ function switchTab(name) { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.tab === name)); document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.toggle('active', panel.id === `tab-${name}`)); localStorage.setItem('activeTab', name); - if (name === 'streams') { - loadPictureSources(); - } else if (name === 'targets') { - loadTargetsTab(); + if (name === 'dashboard') { + loadDashboard(); + startDashboardWS(); + } else { + stopDashboardWS(); + if (name === 'streams') { + loadPictureSources(); + } else if (name === 'targets') { + loadTargetsTab(); + } } } @@ -599,9 +606,8 @@ function initTabs() { let saved = localStorage.getItem('activeTab'); // Migrate legacy 'devices' tab to 'targets' (devices now live inside targets) if (saved === 'devices') saved = 'targets'; - if (saved && document.getElementById(`tab-${saved}`)) { - switchTab(saved); - } + if (!saved || !document.getElementById(`tab-${saved}`)) saved = 'dashboard'; + switchTab(saved); } @@ -1333,14 +1339,251 @@ function startAutoRefresh() { refreshInterval = setInterval(() => { // Only refresh if user is authenticated if (apiKey) { - const activeTab = localStorage.getItem('activeTab') || 'targets'; + const activeTab = localStorage.getItem('activeTab') || 'dashboard'; if (activeTab === 'targets') { loadTargetsTab(); + } else if (activeTab === 'dashboard') { + loadDashboard(); } } }, 2000); // Refresh every 2 seconds } +// ── Dashboard ── + +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 `${h}h ${m}m`; + if (m > 0) return `${m}m ${s}s`; + return `${s}s`; +} + +let _dashboardLoading = false; + +async function loadDashboard() { + if (_dashboardLoading) return; + _dashboardLoading = true; + const container = document.getElementById('dashboard-content'); + if (!container) { _dashboardLoading = false; return; } + + try { + const targetsResp = await fetch(`${API_BASE}/picture-targets`, { headers: getHeaders() }); + if (targetsResp.status === 401) { handle401Error(); return; } + + const targetsData = await targetsResp.json(); + const targets = targetsData.targets || []; + + if (targets.length === 0) { + container.innerHTML = `