diff --git a/server/src/wled_controller/static/js/app.js b/server/src/wled_controller/static/js/app.js
index 51e29b8..2f633b4 100644
--- a/server/src/wled_controller/static/js/app.js
+++ b/server/src/wled_controller/static/js/app.js
@@ -37,6 +37,7 @@ import {
import {
loadDashboard, startDashboardWS, stopDashboardWS,
dashboardToggleProfile, dashboardStartTarget, dashboardStopTarget, dashboardStopAll,
+ toggleDashboardSection,
} from './features/dashboard.js';
import {
loadPictureSources, switchStreamTab,
@@ -151,6 +152,7 @@ Object.assign(window, {
dashboardStartTarget,
dashboardStopTarget,
dashboardStopAll,
+ toggleDashboardSection,
// streams / capture templates / PP templates
loadPictureSources,
diff --git a/server/src/wled_controller/static/js/features/dashboard.js b/server/src/wled_controller/static/js/features/dashboard.js
index 956f4fc..c7244aa 100644
--- a/server/src/wled_controller/static/js/features/dashboard.js
+++ b/server/src/wled_controller/static/js/features/dashboard.js
@@ -8,6 +8,48 @@ import { t } from '../core/i18n.js';
import { escapeHtml, handle401Error } from '../core/api.js';
import { showToast } from '../core/ui.js';
+const DASHBOARD_COLLAPSED_KEY = 'dashboard_collapsed';
+
+function _getCollapsedSections() {
+ try { return JSON.parse(localStorage.getItem(DASHBOARD_COLLAPSED_KEY)) || {}; }
+ catch { return {}; }
+}
+
+export function toggleDashboardSection(sectionKey) {
+ const collapsed = _getCollapsedSections();
+ collapsed[sectionKey] = !collapsed[sectionKey];
+ localStorage.setItem(DASHBOARD_COLLAPSED_KEY, JSON.stringify(collapsed));
+ const header = document.querySelector(`[data-dashboard-section="${sectionKey}"]`);
+ if (!header) return;
+ const content = header.nextElementSibling;
+ const chevron = header.querySelector('.dashboard-section-chevron');
+ if (collapsed[sectionKey]) {
+ content.style.display = 'none';
+ chevron.textContent = '\u25B6';
+ } else {
+ content.style.display = '';
+ chevron.textContent = '\u25BC';
+ }
+}
+
+function _sectionHeader(sectionKey, label, count, extraHtml = '') {
+ const collapsed = _getCollapsedSections();
+ const isCollapsed = !!collapsed[sectionKey];
+ const chevron = isCollapsed ? '\u25B6' : '\u25BC';
+ return `
`;
+}
+
+function _sectionContent(sectionKey, itemsHtml) {
+ const collapsed = _getCollapsedSections();
+ const isCollapsed = !!collapsed[sectionKey];
+ return `${itemsHtml}
`;
+}
+
function formatUptime(seconds) {
if (!seconds || seconds <= 0) return '-';
const h = Math.floor(seconds / 3600);
@@ -67,35 +109,30 @@ export async function loadDashboard() {
if (profiles.length > 0) {
const activeProfiles = profiles.filter(p => p.is_active);
const inactiveProfiles = profiles.filter(p => !p.is_active);
+ const profileItems = [...activeProfiles, ...inactiveProfiles].map(p => renderDashboardProfile(p)).join('');
html += `
-
- ${activeProfiles.map(p => renderDashboardProfile(p)).join('')}
- ${inactiveProfiles.map(p => renderDashboardProfile(p)).join('')}
+ ${_sectionHeader('profiles', t('dashboard.section.profiles'), profiles.length)}
+ ${_sectionContent('profiles', profileItems)}
`;
}
if (running.length > 0) {
+ const stopAllBtn = ``;
+ const runningItems = running.map(target => renderDashboardTarget(target, true, devicesMap)).join('');
+
html += `
-
- ${running.map(target => renderDashboardTarget(target, true, devicesMap)).join('')}
+ ${_sectionHeader('running', t('dashboard.section.running'), running.length, stopAllBtn)}
+ ${_sectionContent('running', runningItems)}
`;
}
if (stopped.length > 0) {
+ const stoppedItems = stopped.map(target => renderDashboardTarget(target, false, devicesMap)).join('');
+
html += `
-
- ${stopped.map(target => renderDashboardTarget(target, false, devicesMap)).join('')}
+ ${_sectionHeader('stopped', t('dashboard.section.stopped'), stopped.length)}
+ ${_sectionContent('stopped', stoppedItems)}
`;
}
diff --git a/server/src/wled_controller/static/style.css b/server/src/wled_controller/static/style.css
index 4c41f44..f7d30cb 100644
--- a/server/src/wled_controller/static/style.css
+++ b/server/src/wled_controller/static/style.css
@@ -3256,6 +3256,15 @@ input:-webkit-autofill:focus {
gap: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
+ cursor: pointer;
+ user-select: none;
+}
+
+.dashboard-section-chevron {
+ font-size: 0.6rem;
+ color: var(--text-secondary);
+ width: 10px;
+ display: inline-block;
}
.dashboard-section-count {