Add WebSocket device type, capability-driven settings, hide filter on collapse
- New WS device type: broadcaster singleton + LEDClient that sends binary
frames to connected WebSocket clients during processing
- FastAPI WS endpoint at /api/v1/devices/{device_id}/ws with token auth
- Frontend: add/edit WS devices, connection URL with copy button in settings
- Add health_check and auto_restore capabilities to WLED and Serial providers;
hide health interval and auto-restore toggle for devices without them
- Skip health check loop for virtual devices (Mock, MQTT, WS) — set always-online
- Copy buttons and labels for API CSS push endpoints (REST POST / WebSocket)
- Hide mock:// and ws:// URLs in target device dropdown
- Hide filter textbox when card section is collapsed (cs-collapsed CSS class)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
import {
|
||||
_deviceBrightnessCache, updateDeviceBrightness,
|
||||
} from '../core/state.js';
|
||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, isSerialDevice, isMockDevice, isMqttDevice } from '../core/api.js';
|
||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, isSerialDevice, isMockDevice, isMqttDevice, isWsDevice } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast, showConfirm } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
@@ -34,6 +34,10 @@ class DeviceSettingsModal extends Modal {
|
||||
const deviceId = this.$('settings-device-id')?.value || '';
|
||||
return `mock://${deviceId}`;
|
||||
}
|
||||
if (isWsDevice(this.deviceType)) {
|
||||
const deviceId = this.$('settings-device-id')?.value || '';
|
||||
return `ws://${deviceId}`;
|
||||
}
|
||||
if (isSerialDevice(this.deviceType)) {
|
||||
return this.$('settings-serial-port').value;
|
||||
}
|
||||
@@ -83,7 +87,7 @@ export function createDeviceCard(device) {
|
||||
<div class="card-title">
|
||||
<span class="health-dot ${healthClass}" title="${healthTitle}"></span>
|
||||
${device.name || device.id}
|
||||
${device.url && device.url.startsWith('http') ? `<a class="device-url-badge" href="${device.url}" target="_blank" rel="noopener" title="${t('device.button.webui')}"><span class="device-url-text">${escapeHtml(device.url.replace(/^https?:\/\//, ''))}</span><span class="device-url-icon">${ICON_WEB}</span></a>` : (device.url && !device.url.startsWith('mock://') && !device.url.startsWith('http') ? `<span class="device-url-badge"><span class="device-url-text">${escapeHtml(device.url)}</span></span>` : '')}
|
||||
${device.url && device.url.startsWith('http') ? `<a class="device-url-badge" href="${device.url}" target="_blank" rel="noopener" title="${t('device.button.webui')}"><span class="device-url-text">${escapeHtml(device.url.replace(/^https?:\/\//, ''))}</span><span class="device-url-icon">${ICON_WEB}</span></a>` : (device.url && !device.url.startsWith('mock://') && !device.url.startsWith('ws://') && !device.url.startsWith('http') ? `<span class="device-url-badge"><span class="device-url-text">${escapeHtml(device.url)}</span></span>` : '')}
|
||||
${healthLabel}
|
||||
</div>
|
||||
</div>
|
||||
@@ -174,13 +178,14 @@ export async function showSettings(deviceId) {
|
||||
document.getElementById('settings-health-interval').value = device.state_check_interval ?? 30;
|
||||
|
||||
const isMock = isMockDevice(device.device_type);
|
||||
const isWs = isWsDevice(device.device_type);
|
||||
const isMqtt = isMqttDevice(device.device_type);
|
||||
const urlGroup = document.getElementById('settings-url-group');
|
||||
const serialGroup = document.getElementById('settings-serial-port-group');
|
||||
const urlLabel = urlGroup.querySelector('label[for="settings-device-url"]');
|
||||
const urlHint = urlGroup.querySelector('.input-hint');
|
||||
const urlInput = document.getElementById('settings-device-url');
|
||||
if (isMock) {
|
||||
if (isMock || isWs) {
|
||||
urlGroup.style.display = 'none';
|
||||
urlInput.removeAttribute('required');
|
||||
serialGroup.style.display = 'none';
|
||||
@@ -245,6 +250,31 @@ export async function showSettings(deviceId) {
|
||||
if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
|
||||
}
|
||||
|
||||
// WS connection URL
|
||||
const wsUrlGroup = document.getElementById('settings-ws-url-group');
|
||||
if (wsUrlGroup) {
|
||||
if (isWs) {
|
||||
const wsProto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const apiKey = localStorage.getItem('wled_api_key') || '';
|
||||
const wsUrl = `${wsProto}//${location.host}/api/v1/devices/${device.id}/ws?token=${encodeURIComponent(apiKey)}`;
|
||||
document.getElementById('settings-ws-url').value = wsUrl;
|
||||
wsUrlGroup.style.display = '';
|
||||
} else {
|
||||
wsUrlGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Hide health check for devices without health_check capability
|
||||
const healthIntervalGroup = document.getElementById('settings-health-interval-group');
|
||||
if (healthIntervalGroup) {
|
||||
healthIntervalGroup.style.display = caps.includes('health_check') ? '' : 'none';
|
||||
}
|
||||
|
||||
// Hide auto-restore for devices without auto_restore capability
|
||||
const autoShutdownGroup = document.getElementById('settings-auto-shutdown-group');
|
||||
if (autoShutdownGroup) {
|
||||
autoShutdownGroup.style.display = caps.includes('auto_restore') ? '' : 'none';
|
||||
}
|
||||
document.getElementById('settings-auto-shutdown').checked = !!device.auto_shutdown;
|
||||
settingsModal.snapshot();
|
||||
settingsModal.open();
|
||||
@@ -442,6 +472,15 @@ async function _populateSettingsSerialPorts(currentUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
export function copyWsUrl() {
|
||||
const input = document.getElementById('settings-ws-url');
|
||||
if (input && input.value) {
|
||||
navigator.clipboard.writeText(input.value).then(() => {
|
||||
showToast(t('settings.copied') || 'Copied!', 'success');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadDevices() {
|
||||
await window.loadTargetsTab();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user