Add LEDDeviceProvider abstraction and standby capability flag

Consolidate all device-type-specific logic into LEDDeviceProvider ABC
with provider registry. WLEDDeviceProvider handles client creation,
health checks, validation, mDNS discovery, and brightness control.
Routes now delegate to providers instead of using if/else type checks.

Add standby_required capability and expose device capabilities in API.
Target editor conditionally shows standby interval based on selected
device's capabilities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 13:39:27 +03:00
parent 638dc526f9
commit 242718a9a9
7 changed files with 362 additions and 205 deletions

View File

@@ -873,6 +873,8 @@ async function fetchDeviceBrightness(deviceId) {
}
// Add device modal
let _discoveryScanRunning = false;
function showAddDevice() {
const modal = document.getElementById('add-device-modal');
const form = document.getElementById('add-device-form');
@@ -892,6 +894,8 @@ function showAddDevice() {
modal.style.display = 'flex';
lockBody();
setTimeout(() => document.getElementById('device-name').focus(), 100);
// Auto-start discovery on open
scanForDevices();
}
function closeAddDeviceModal() {
@@ -901,6 +905,9 @@ function closeAddDeviceModal() {
}
async function scanForDevices() {
if (_discoveryScanRunning) return;
_discoveryScanRunning = true;
const loading = document.getElementById('discovery-loading');
const list = document.getElementById('discovery-list');
const empty = document.getElementById('discovery-empty');
@@ -962,6 +969,8 @@ async function scanForDevices() {
empty.style.display = 'block';
empty.querySelector('small').textContent = t('device.scan.error');
console.error('Device scan failed:', err);
} finally {
_discoveryScanRunning = false;
}
}
@@ -3906,6 +3915,15 @@ function closePPTemplateModal() {
// ===== TARGET EDITOR MODAL =====
let targetEditorInitialValues = {};
let _targetEditorDevices = []; // cached devices list for capability checks
function _updateStandbyVisibility() {
const deviceSelect = document.getElementById('target-editor-device');
const standbyGroup = document.getElementById('target-editor-standby-group');
const selectedDevice = _targetEditorDevices.find(d => d.id === deviceSelect.value);
const caps = selectedDevice?.capabilities || [];
standbyGroup.style.display = caps.includes('standby_required') ? '' : 'none';
}
async function showTargetEditor(targetId = null) {
try {
@@ -3917,6 +3935,7 @@ async function showTargetEditor(targetId = null) {
const devices = devicesResp.ok ? (await devicesResp.json()).devices || [] : [];
const sources = sourcesResp.ok ? (await sourcesResp.json()).streams || [] : [];
_targetEditorDevices = devices;
// Populate device select
const deviceSelect = document.getElementById('target-editor-device');
@@ -3929,6 +3948,7 @@ async function showTargetEditor(targetId = null) {
opt.textContent = `${d.name} [${devType}]${shortUrl ? ' (' + shortUrl + ')' : ''}`;
deviceSelect.appendChild(opt);
});
deviceSelect.onchange = _updateStandbyVisibility;
// Populate source select
const sourceSelect = document.getElementById('target-editor-source');
@@ -3975,6 +3995,9 @@ async function showTargetEditor(targetId = null) {
document.getElementById('target-editor-title').textContent = t('targets.add');
}
// Show/hide standby interval based on selected device capabilities
_updateStandbyVisibility();
targetEditorInitialValues = {
name: document.getElementById('target-editor-name').value,
device: deviceSelect.value,