feat: HA light output targets — cast LED colors to Home Assistant lights
Lint & Test / test (push) Has been cancelled

New output target type `ha_light` that sends averaged LED colors to HA
light entities via WebSocket service calls (light.turn_on/turn_off):

Backend:
- HARuntime.call_service(): fire-and-forget WS service calls
- HALightOutputTarget: data model with light mappings, update rate, transition
- HALightTargetProcessor: processing loop with delta detection, rate limiting
- ProcessorManager.add_ha_light_target(): registration
- API schemas/routes updated for ha_light target type

Frontend:
- HA Light Targets section in Targets tab tree nav
- Modal editor: HA source picker, CSS source picker, light entity mappings
- Target cards with start/stop/clone/edit actions
- i18n keys for all new UI strings
This commit is contained in:
2026-03-28 00:08:49 +03:00
parent fb98e6e2b8
commit cb9289f01f
25 changed files with 1679 additions and 164 deletions
@@ -6,7 +6,7 @@ import { apiKey, _dashboardLoading, set_dashboardLoading, dashboardPollInterval,
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, fetchMetricsHistory } from '../core/api.ts';
import { t } from '../core/i18n.ts';
import { showToast, showConfirm, formatUptime, formatCompact, setTabRefreshing } from '../core/ui.ts';
import { renderPerfSection, initPerfCharts, startPerfPolling, stopPerfPolling } from './perf-charts.ts';
import { renderPerfSection, renderPerfModeToggle, setPerfMode, initPerfCharts, startPerfPolling, stopPerfPolling } from './perf-charts.ts';
import { startAutoRefresh, updateTabBadge } from './tabs.ts';
import {
ICON_TARGET, ICON_AUTOMATION, ICON_CLOCK, ICON_WARNING, ICON_OK,
@@ -511,7 +511,7 @@ export async function loadDashboard(forceFullRender: boolean = false): Promise<v
const toolbar = `<div class="stream-tab-bar"><span class="cs-expand-collapse-group">${pollSelect}<button class="tutorial-trigger-btn" onclick="startDashboardTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
if (isFirstLoad) {
container.innerHTML = `${toolbar}<div class="dashboard-perf-persistent dashboard-section">
${_sectionHeader('perf', t('dashboard.section.performance'), '')}
${_sectionHeader('perf', t('dashboard.section.performance'), '', renderPerfModeToggle())}
${_sectionContent('perf', renderPerfSection())}
</div>
<div class="dashboard-dynamic">${dynamicHtml}</div>`;