From dc4495a117e0714e4780719c2ffe33b25496fffd Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Mon, 9 Mar 2026 00:27:08 +0300 Subject: [PATCH] Add collapsible pipeline metrics and error indicator to target cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FPS chart stays always visible; timing, frames, keepalive, errors, and uptime are collapsed behind an animated toggle. Error warning icon appears next to target name when errors_count > 0. Uses CSS grid 0fr→1fr transition for smooth expand/collapse animation. Co-Authored-By: Claude Opus 4.6 --- .../src/wled_controller/static/css/cards.css | 48 +++++++++++++++++ .../static/js/features/targets.js | 54 ++++++++++++------- .../wled_controller/static/locales/en.json | 1 + .../wled_controller/static/locales/ru.json | 1 + .../wled_controller/static/locales/zh.json | 1 + server/src/wled_controller/static/sw.js | 2 +- 6 files changed, 86 insertions(+), 21 deletions(-) diff --git a/server/src/wled_controller/static/css/cards.css b/server/src/wled_controller/static/css/cards.css index a2584e5..89a4502 100644 --- a/server/src/wled_controller/static/css/cards.css +++ b/server/src/wled_controller/static/css/cards.css @@ -742,6 +742,54 @@ ul.section-tip li { color: #ff5252; } +/* Error indicator next to target name */ +.target-error-indicator { + display: none; + color: var(--danger-color); + margin-left: 4px; + vertical-align: middle; +} +.target-error-indicator .icon { + width: 14px; + height: 14px; +} +.target-error-indicator.visible { + display: inline-flex; + align-items: center; +} + +/* Collapsible target pipeline metrics */ +.target-metrics-collapse { + margin-top: 4px; +} +.target-metrics-toggle { + cursor: pointer; + font-size: 0.75rem; + color: var(--text-secondary); + padding: 4px 0; + user-select: none; + background: none; + border: none; + font-family: inherit; +} +.target-metrics-toggle::before { + content: '▸ '; +} +.target-metrics-collapse.open .target-metrics-toggle::before { + content: '▾ '; +} +.target-metrics-animate { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.25s ease; +} +.target-metrics-collapse.open .target-metrics-animate { + grid-template-rows: 1fr; +} +.target-metrics-animate > .target-metrics-expanded { + overflow: hidden; +} + /* Timing breakdown bar */ .timing-breakdown { margin-top: 8px; diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index 9b05946..803f4d0 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -24,6 +24,7 @@ import { ICON_CLONE, ICON_EDIT, ICON_START, ICON_STOP, ICON_LED, ICON_FPS, ICON_OVERLAY, ICON_LED_PREVIEW, ICON_GLOBE, ICON_RADIO, ICON_PLUG, ICON_FILM, ICON_SUN_DIM, ICON_TARGET_ICON, ICON_HELP, + ICON_WARNING, } from '../core/icons.js'; import { EntitySelect } from '../core/entity-palette.js'; import { wrapCard } from '../core/card-colors.js'; @@ -881,6 +882,13 @@ function _patchTargetMetrics(target) { const errors = card.querySelector('[data-tm="errors"]'); if (errors) errors.textContent = metrics.errors_count || 0; + // Error indicator near target name + const errorIndicator = card.querySelector('.target-error-indicator'); + if (errorIndicator) { + const hasErrors = (metrics.errors_count || 0) > 0; + errorIndicator.classList.toggle('visible', hasErrors); + } + const uptime = card.querySelector('[data-tm="uptime"]'); if (uptime) uptime.textContent = formatUptime(metrics.uptime_seconds); } @@ -922,6 +930,7 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
${escapeHtml(target.name)} + ${ICON_WARNING}
@@ -943,26 +952,31 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo ---
- ${state.timing_total_ms != null ? ` -
- ` : ''} -
-
${t('device.metrics.frames')}
-
---
-
- ${state.needs_keepalive !== false ? ` -
-
${t('device.metrics.keepalive')}
-
---
-
- ` : ''} -
-
${t('device.metrics.errors')}
-
---
-
-
-
${t('device.metrics.uptime')}
-
---
+
+
+ +
+
+
+
+
${t('device.metrics.frames')}
+
---
+
+ ${state.needs_keepalive !== false ? ` +
+
${t('device.metrics.keepalive')}
+
---
+
+ ` : ''} +
+
${t('device.metrics.errors')}
+
---
+
+
+
${t('device.metrics.uptime')}
+
---
+
+
` : ''} diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json index 925fce4..f9e5aaa 100644 --- a/server/src/wled_controller/static/locales/en.json +++ b/server/src/wled_controller/static/locales/en.json @@ -458,6 +458,7 @@ "targets.source": "Source:", "targets.source.hint": "Which picture source to capture and process", "targets.source.none": "-- No source assigned --", + "targets.metrics.pipeline": "Pipeline details", "targets.fps": "Target FPS:", "targets.fps.hint": "Target frames per second for capture and LED updates (1-90)", "targets.fps.rec": "Hardware max ≈ {fps} fps ({leds} LEDs)", diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json index 683f3fd..3419033 100644 --- a/server/src/wled_controller/static/locales/ru.json +++ b/server/src/wled_controller/static/locales/ru.json @@ -458,6 +458,7 @@ "targets.source": "Источник:", "targets.source.hint": "Какой источник изображения захватывать и обрабатывать", "targets.source.none": "-- Источник не назначен --", + "targets.metrics.pipeline": "Детали конвейера", "targets.fps": "Целевой FPS:", "targets.fps.hint": "Целевая частота кадров для захвата и обновления LED (1-90)", "targets.fps.rec": "Макс. аппаратный ≈ {fps} fps ({leds} LED)", diff --git a/server/src/wled_controller/static/locales/zh.json b/server/src/wled_controller/static/locales/zh.json index dd8ba26..0d8cb01 100644 --- a/server/src/wled_controller/static/locales/zh.json +++ b/server/src/wled_controller/static/locales/zh.json @@ -458,6 +458,7 @@ "targets.source": "源:", "targets.source.hint": "要采集和处理的图片源", "targets.source.none": "-- 未分配源 --", + "targets.metrics.pipeline": "管线详情", "targets.fps": "目标 FPS:", "targets.fps.hint": "采集和 LED 更新的目标帧率(1-90)", "targets.fps.rec": "硬件最大 ≈ {fps} fps({leds} 个 LED)", diff --git a/server/src/wled_controller/static/sw.js b/server/src/wled_controller/static/sw.js index b4111d2..ed6b1dd 100644 --- a/server/src/wled_controller/static/sw.js +++ b/server/src/wled_controller/static/sw.js @@ -7,7 +7,7 @@ * - Navigation: network-first with offline fallback */ -const CACHE_NAME = 'ledgrab-v17'; +const CACHE_NAME = 'ledgrab-v18'; // Only pre-cache static assets (no auth required). // Do NOT pre-cache '/' — it requires API key auth and would cache an error page.