Rework API input CSS: segments, remove led_count, HAOS light, test preview
API Input CSS rework:
- Remove led_count field from ApiInputColorStripSource (always auto-sizes)
- Add segment-based payload: solid, per_pixel, gradient modes
- Segments applied in order (last wins on overlap), auto-grow buffer
- Backward compatible: legacy {"colors": [...]} still works
- Pydantic validation: mode-specific field requirements
Test preview:
- Enable test preview button on api_input cards
- Hide LED/FPS controls for api_input (sender controls those)
- Show input source selector for all CSS tests (preselected)
- FPS sparkline chart using shared createFpsSparkline (same as target cards)
- Server only sends frames when push_generation changes (no idle frames)
HAOS integration:
- New light.py: ApiInputLight entity per api_input source (RGB + brightness)
- turn_on pushes solid segment, turn_off pushes fallback color
- Register wled_screen_controller.set_leds service for arbitrary segments
- New services.yaml with field definitions
- Coordinator: push_colors() and push_segments() methods
- Platform.LIGHT added to platforms list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,219 +1,221 @@
|
||||
<!-- Settings Modal -->
|
||||
<div id="settings-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="settings-modal-title">
|
||||
<div class="modal-content" style="max-width: 450px;">
|
||||
<div class="modal-content" style="max-width: 480px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="settings-modal-title" data-i18n="settings.title">Settings</h2>
|
||||
<button class="modal-close-btn" onclick="closeSettingsModal()" title="Close" data-i18n-aria-label="aria.close">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab bar -->
|
||||
<div class="settings-tab-bar">
|
||||
<button class="settings-tab-btn active" data-settings-tab="general" onclick="switchSettingsTab('general')" data-i18n="settings.tab.general">General</button>
|
||||
<button class="settings-tab-btn" data-settings-tab="backup" onclick="switchSettingsTab('backup')" data-i18n="settings.tab.backup">Backup</button>
|
||||
<button class="settings-tab-btn" data-settings-tab="mqtt" onclick="switchSettingsTab('mqtt')" data-i18n="settings.tab.mqtt">MQTT</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- API Keys section (read-only) -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.api_keys.label">API Keys</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
<!-- ═══ General tab ═══ -->
|
||||
<div id="settings-panel-general" class="settings-panel active">
|
||||
<!-- API Keys section (read-only) -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.api_keys.label">API Keys</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.api_keys.hint">API keys are defined in the server config file (config.yaml). Restart the server after editing the file to apply changes.</small>
|
||||
<div id="settings-api-keys-list" style="font-size:0.85rem;"></div>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.api_keys.hint">API keys are defined in the server config file (config.yaml). Restart the server after editing the file to apply changes.</small>
|
||||
<div id="settings-api-keys-list" style="font-size:0.85rem;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Backup section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.backup.label">Backup Configuration</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.backup.hint">Download all configuration (devices, targets, streams, templates, automations) as a single JSON file.</small>
|
||||
<button class="btn btn-primary" onclick="downloadBackup()" style="width:100%" data-i18n="settings.backup.button">Download Backup</button>
|
||||
</div>
|
||||
|
||||
<!-- Restore section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.restore.label">Restore Configuration</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.restore.hint">Upload a previously downloaded backup file to replace all configuration. The server will restart automatically.</small>
|
||||
<input type="file" id="settings-restore-input" accept=".json" style="display:none" onchange="handleRestoreFileSelected(this)">
|
||||
<button class="btn btn-danger" onclick="document.getElementById('settings-restore-input').click()" style="width:100%" data-i18n="settings.restore.button">Restore from Backup</button>
|
||||
</div>
|
||||
|
||||
<!-- Partial Export/Import section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.partial.label">Partial Export / Import</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.partial.hint">Export or import a single entity type. Import replaces or merges existing data and restarts the server.</small>
|
||||
|
||||
<div style="display:flex;gap:0.5rem;margin-bottom:0.5rem;">
|
||||
<select id="settings-partial-store" style="flex:1">
|
||||
<option value="devices" data-i18n="settings.partial.store.devices">Devices</option>
|
||||
<option value="output_targets" data-i18n="settings.partial.store.output_targets">LED Targets</option>
|
||||
<option value="color_strip_sources" data-i18n="settings.partial.store.color_strip_sources">Color Strips</option>
|
||||
<option value="picture_sources" data-i18n="settings.partial.store.picture_sources">Picture Sources</option>
|
||||
<option value="audio_sources" data-i18n="settings.partial.store.audio_sources">Audio Sources</option>
|
||||
<option value="audio_templates" data-i18n="settings.partial.store.audio_templates">Audio Templates</option>
|
||||
<option value="capture_templates" data-i18n="settings.partial.store.capture_templates">Capture Templates</option>
|
||||
<option value="postprocessing_templates" data-i18n="settings.partial.store.postprocessing_templates">Post-processing Templates</option>
|
||||
<option value="color_strip_processing_templates" data-i18n="settings.partial.store.color_strip_processing_templates">CSS Processing Templates</option>
|
||||
<option value="pattern_templates" data-i18n="settings.partial.store.pattern_templates">Pattern Templates</option>
|
||||
<option value="value_sources" data-i18n="settings.partial.store.value_sources">Value Sources</option>
|
||||
<option value="sync_clocks" data-i18n="settings.partial.store.sync_clocks">Sync Clocks</option>
|
||||
<option value="automations" data-i18n="settings.partial.store.automations">Automations</option>
|
||||
<option value="scene_presets" data-i18n="settings.partial.store.scene_presets">Scene Presets</option>
|
||||
<!-- Log Level section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.log_level.label">Log Level</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.log_level.hint">Change the server log verbosity at runtime. DEBUG shows the most detail; CRITICAL shows only fatal errors.</small>
|
||||
<select id="settings-log-level">
|
||||
<option value="DEBUG">DEBUG</option>
|
||||
<option value="INFO">INFO</option>
|
||||
<option value="WARNING">WARNING</option>
|
||||
<option value="ERROR">ERROR</option>
|
||||
<option value="CRITICAL">CRITICAL</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" onclick="downloadPartialExport()" data-i18n="settings.partial.export_button">Export</button>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;">
|
||||
<input type="checkbox" id="settings-partial-merge">
|
||||
<label for="settings-partial-merge" style="margin:0;font-size:0.85rem;" data-i18n="settings.partial.merge_label">Merge (add/overwrite, keep existing)</label>
|
||||
<!-- Server Logs button (opens overlay) -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.logs.label">Server Logs</label>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="openLogOverlay()" style="width:100%" data-i18n="settings.logs.open_viewer">Open Log Viewer</button>
|
||||
</div>
|
||||
|
||||
<input type="file" id="settings-partial-import-input" accept=".json" style="display:none" onchange="handlePartialImportFileSelected(this)">
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('settings-partial-import-input').click()" style="width:100%" data-i18n="settings.partial.import_button">Import from File</button>
|
||||
<!-- Restart section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.restart_server">Restart Server</label>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="restartServer()" style="width:100%" data-i18n="settings.restart_server">Restart Server</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Backup section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.auto_backup.label">Auto-Backup</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.auto_backup.hint">Automatically create periodic backups of all configuration. Old backups are pruned when the maximum count is reached.</small>
|
||||
|
||||
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<input type="checkbox" id="auto-backup-enabled">
|
||||
<label for="auto-backup-enabled" style="margin:0" data-i18n="settings.auto_backup.enable">Enable auto-backup</label>
|
||||
<!-- ═══ Backup tab ═══ -->
|
||||
<div id="settings-panel-backup" class="settings-panel">
|
||||
<!-- Backup section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.backup.label">Backup Configuration</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.backup.hint">Download all configuration (devices, targets, streams, templates, automations) as a single JSON file.</small>
|
||||
<button class="btn btn-primary" onclick="downloadBackup()" style="width:100%" data-i18n="settings.backup.button">Download Backup</button>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<div style="flex:1">
|
||||
<label for="auto-backup-interval" style="font-size:0.85rem" data-i18n="settings.auto_backup.interval_label">Interval</label>
|
||||
<select id="auto-backup-interval" style="width:100%">
|
||||
<option value="1">1h</option>
|
||||
<option value="6">6h</option>
|
||||
<option value="12">12h</option>
|
||||
<option value="24">24h</option>
|
||||
<option value="48">48h</option>
|
||||
<option value="168">7d</option>
|
||||
<!-- Restore section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.restore.label">Restore Configuration</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.restore.hint">Upload a previously downloaded backup file to replace all configuration. The server will restart automatically.</small>
|
||||
<input type="file" id="settings-restore-input" accept=".json" style="display:none" onchange="handleRestoreFileSelected(this)">
|
||||
<button class="btn btn-danger" onclick="document.getElementById('settings-restore-input').click()" style="width:100%" data-i18n="settings.restore.button">Restore from Backup</button>
|
||||
</div>
|
||||
|
||||
<!-- Partial Export/Import section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.partial.label">Partial Export / Import</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.partial.hint">Export or import a single entity type. Import replaces or merges existing data and restarts the server.</small>
|
||||
|
||||
<div style="display:flex;gap:0.5rem;margin-bottom:0.5rem;">
|
||||
<select id="settings-partial-store" style="flex:1">
|
||||
<option value="devices" data-i18n="settings.partial.store.devices">Devices</option>
|
||||
<option value="output_targets" data-i18n="settings.partial.store.output_targets">LED Targets</option>
|
||||
<option value="color_strip_sources" data-i18n="settings.partial.store.color_strip_sources">Color Strips</option>
|
||||
<option value="picture_sources" data-i18n="settings.partial.store.picture_sources">Picture Sources</option>
|
||||
<option value="audio_sources" data-i18n="settings.partial.store.audio_sources">Audio Sources</option>
|
||||
<option value="audio_templates" data-i18n="settings.partial.store.audio_templates">Audio Templates</option>
|
||||
<option value="capture_templates" data-i18n="settings.partial.store.capture_templates">Capture Templates</option>
|
||||
<option value="postprocessing_templates" data-i18n="settings.partial.store.postprocessing_templates">Post-processing Templates</option>
|
||||
<option value="color_strip_processing_templates" data-i18n="settings.partial.store.color_strip_processing_templates">CSS Processing Templates</option>
|
||||
<option value="pattern_templates" data-i18n="settings.partial.store.pattern_templates">Pattern Templates</option>
|
||||
<option value="value_sources" data-i18n="settings.partial.store.value_sources">Value Sources</option>
|
||||
<option value="sync_clocks" data-i18n="settings.partial.store.sync_clocks">Sync Clocks</option>
|
||||
<option value="automations" data-i18n="settings.partial.store.automations">Automations</option>
|
||||
<option value="scene_presets" data-i18n="settings.partial.store.scene_presets">Scene Presets</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" onclick="downloadPartialExport()" data-i18n="settings.partial.export_button">Export</button>
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label for="auto-backup-max" style="font-size:0.85rem" data-i18n="settings.auto_backup.max_label">Max backups</label>
|
||||
<input type="number" id="auto-backup-max" min="1" max="100" value="10" style="width:100%">
|
||||
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;">
|
||||
<input type="checkbox" id="settings-partial-merge">
|
||||
<label for="settings-partial-merge" style="margin:0;font-size:0.85rem;" data-i18n="settings.partial.merge_label">Merge (add/overwrite, keep existing)</label>
|
||||
</div>
|
||||
|
||||
<input type="file" id="settings-partial-import-input" accept=".json" style="display:none" onchange="handlePartialImportFileSelected(this)">
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('settings-partial-import-input').click()" style="width:100%" data-i18n="settings.partial.import_button">Import from File</button>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="saveAutoBackupSettings()" style="width:100%" data-i18n="settings.auto_backup.save">Save Settings</button>
|
||||
<!-- Auto-Backup section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.auto_backup.label">Auto-Backup</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.auto_backup.hint">Automatically create periodic backups of all configuration. Old backups are pruned when the maximum count is reached.</small>
|
||||
|
||||
<div id="auto-backup-status" style="font-size:0.85rem; color:var(--text-muted); margin-top:0.5rem;"></div>
|
||||
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<input type="checkbox" id="auto-backup-enabled">
|
||||
<label for="auto-backup-enabled" style="margin:0" data-i18n="settings.auto_backup.enable">Enable auto-backup</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<div style="flex:1">
|
||||
<label for="auto-backup-interval" style="font-size:0.85rem" data-i18n="settings.auto_backup.interval_label">Interval</label>
|
||||
<select id="auto-backup-interval" style="width:100%">
|
||||
<option value="1">1h</option>
|
||||
<option value="6">6h</option>
|
||||
<option value="12">12h</option>
|
||||
<option value="24">24h</option>
|
||||
<option value="48">48h</option>
|
||||
<option value="168">7d</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label for="auto-backup-max" style="font-size:0.85rem" data-i18n="settings.auto_backup.max_label">Max backups</label>
|
||||
<input type="number" id="auto-backup-max" min="1" max="100" value="10" style="width:100%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="saveAutoBackupSettings()" style="width:100%" data-i18n="settings.auto_backup.save">Save Settings</button>
|
||||
|
||||
<div id="auto-backup-status" style="font-size:0.85rem; color:var(--text-muted); margin-top:0.5rem;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Saved Backups section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.saved_backups.label">Saved Backups</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.saved_backups.hint">Auto-backup files stored on the server. Download to save locally, or delete to free space.</small>
|
||||
<div id="saved-backups-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Saved Backups section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.saved_backups.label">Saved Backups</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.saved_backups.hint">Auto-backup files stored on the server. Download to save locally, or delete to free space.</small>
|
||||
<div id="saved-backups-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- MQTT section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.mqtt.label">MQTT</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.mqtt.hint">Configure MQTT broker connection for automation conditions and triggers.</small>
|
||||
|
||||
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.75rem;">
|
||||
<input type="checkbox" id="mqtt-enabled">
|
||||
<label for="mqtt-enabled" style="margin:0" data-i18n="settings.mqtt.enabled">Enable MQTT</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-host" style="font-size:0.85rem" data-i18n="settings.mqtt.host_label">Broker Host</label>
|
||||
<input type="text" id="mqtt-host" placeholder="localhost" style="width:100%">
|
||||
<!-- ═══ MQTT tab ═══ -->
|
||||
<div id="settings-panel-mqtt" class="settings-panel">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.mqtt.label">MQTT</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<div style="width:90px">
|
||||
<label for="mqtt-port" style="font-size:0.85rem" data-i18n="settings.mqtt.port_label">Port</label>
|
||||
<input type="number" id="mqtt-port" min="1" max="65535" value="1883" style="width:100%">
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.mqtt.hint">Configure MQTT broker connection for automation conditions and triggers.</small>
|
||||
|
||||
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.75rem;">
|
||||
<input type="checkbox" id="mqtt-enabled">
|
||||
<label for="mqtt-enabled" style="margin:0" data-i18n="settings.mqtt.enabled">Enable MQTT</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-username" style="font-size:0.85rem" data-i18n="settings.mqtt.username_label">Username</label>
|
||||
<input type="text" id="mqtt-username" placeholder="" autocomplete="off" style="width:100%">
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-host" style="font-size:0.85rem" data-i18n="settings.mqtt.host_label">Broker Host</label>
|
||||
<input type="text" id="mqtt-host" placeholder="localhost" style="width:100%">
|
||||
</div>
|
||||
<div style="width:90px">
|
||||
<label for="mqtt-port" style="font-size:0.85rem" data-i18n="settings.mqtt.port_label">Port</label>
|
||||
<input type="number" id="mqtt-port" min="1" max="65535" value="1883" style="width:100%">
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-password" style="font-size:0.85rem" data-i18n="settings.mqtt.password_label">Password</label>
|
||||
<input type="password" id="mqtt-password" placeholder="" autocomplete="new-password" style="width:100%">
|
||||
<small id="mqtt-password-hint" style="display:none;font-size:0.75rem;color:var(--text-muted)" data-i18n="settings.mqtt.password_set_hint">Password is set — leave blank to keep</small>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.5rem;">
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-username" style="font-size:0.85rem" data-i18n="settings.mqtt.username_label">Username</label>
|
||||
<input type="text" id="mqtt-username" placeholder="" autocomplete="off" style="width:100%">
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-password" style="font-size:0.85rem" data-i18n="settings.mqtt.password_label">Password</label>
|
||||
<input type="password" id="mqtt-password" placeholder="" autocomplete="new-password" style="width:100%">
|
||||
<small id="mqtt-password-hint" style="display:none;font-size:0.75rem;color:var(--text-muted)" data-i18n="settings.mqtt.password_set_hint">Password is set — leave blank to keep</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.75rem;">
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-client-id" style="font-size:0.85rem" data-i18n="settings.mqtt.client_id_label">Client ID</label>
|
||||
<input type="text" id="mqtt-client-id" placeholder="ledgrab" style="width:100%">
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-base-topic" style="font-size:0.85rem" data-i18n="settings.mqtt.base_topic_label">Base Topic</label>
|
||||
<input type="text" id="mqtt-base-topic" placeholder="ledgrab" style="width:100%">
|
||||
<div style="display:flex; gap:0.5rem; margin-bottom:0.75rem;">
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-client-id" style="font-size:0.85rem" data-i18n="settings.mqtt.client_id_label">Client ID</label>
|
||||
<input type="text" id="mqtt-client-id" placeholder="ledgrab" style="width:100%">
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label for="mqtt-base-topic" style="font-size:0.85rem" data-i18n="settings.mqtt.base_topic_label">Base Topic</label>
|
||||
<input type="text" id="mqtt-base-topic" placeholder="ledgrab" style="width:100%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="saveMqttSettings()" style="width:100%" data-i18n="settings.mqtt.save">Save MQTT Settings</button>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="saveMqttSettings()" style="width:100%" data-i18n="settings.mqtt.save">Save MQTT Settings</button>
|
||||
</div>
|
||||
|
||||
<!-- Server Logs section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.logs.label">Server Logs</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.logs.hint">Stream live server log output. Use the filter to show only relevant log levels.</small>
|
||||
|
||||
<div style="display:flex; gap:0.5rem; align-items:center; margin-bottom:0.5rem;">
|
||||
<button id="log-viewer-connect-btn" class="btn btn-secondary" onclick="connectLogViewer()" data-i18n="settings.logs.connect">Connect</button>
|
||||
<button class="btn btn-secondary" onclick="clearLogViewer()" data-i18n="settings.logs.clear">Clear</button>
|
||||
<select id="log-viewer-filter" onchange="applyLogFilter()" style="flex:1; font-size:0.85rem;">
|
||||
<option value="all" data-i18n="settings.logs.filter.all">All</option>
|
||||
<option value="INFO" data-i18n="settings.logs.filter.info">Info+</option>
|
||||
<option value="WARNING" data-i18n="settings.logs.filter.warning">Warning+</option>
|
||||
<option value="ERROR" data-i18n="settings.logs.filter.error">Error only</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<pre id="log-viewer-output" class="log-viewer-output"></pre>
|
||||
</div>
|
||||
|
||||
<!-- Log Level section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.log_level.label">Log Level</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.log_level.hint">Change the server log verbosity at runtime. DEBUG shows the most detail; CRITICAL shows only fatal errors.</small>
|
||||
<select id="settings-log-level">
|
||||
<option value="DEBUG">DEBUG</option>
|
||||
<option value="INFO">INFO</option>
|
||||
<option value="WARNING">WARNING</option>
|
||||
<option value="ERROR">ERROR</option>
|
||||
<option value="CRITICAL">CRITICAL</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Restart section -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.restart_server">Restart Server</label>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="restartServer()" style="width:100%" data-i18n="settings.restart_server">Restart Server</button>
|
||||
</div>
|
||||
|
||||
<div id="settings-error" class="error-message" style="display:none;"></div>
|
||||
@@ -223,3 +225,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Viewer Overlay (full-screen, independent of settings modal) -->
|
||||
<div id="log-overlay" class="log-overlay" style="display:none;">
|
||||
<button class="log-overlay-close" onclick="closeLogOverlay()" title="Close" data-i18n-aria-label="aria.close">✕</button>
|
||||
<div class="log-overlay-toolbar">
|
||||
<h3 data-i18n="settings.logs.label">Server Logs</h3>
|
||||
<select id="log-viewer-filter" onchange="applyLogFilter()">
|
||||
<option value="all" data-i18n="settings.logs.filter.all">All levels</option>
|
||||
<option value="INFO" data-i18n="settings.logs.filter.info">Info+</option>
|
||||
<option value="WARNING" data-i18n="settings.logs.filter.warning">Warning+</option>
|
||||
<option value="ERROR" data-i18n="settings.logs.filter.error">Error only</option>
|
||||
</select>
|
||||
<button id="log-viewer-connect-btn" class="btn btn-secondary btn-sm" onclick="connectLogViewer()" data-i18n="settings.logs.connect">Connect</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="clearLogViewer()" data-i18n="settings.logs.clear">Clear</button>
|
||||
</div>
|
||||
<pre id="log-viewer-output" class="log-viewer-output"></pre>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
|
||||
<!-- LED count & FPS controls -->
|
||||
<div class="css-test-led-control">
|
||||
<div id="css-test-led-fps-group" class="css-test-led-control">
|
||||
<span id="css-test-led-group">
|
||||
<label for="css-test-led-input" data-i18n="color_strip.test.led_count">LEDs:</label>
|
||||
<input type="number" id="css-test-led-input" min="1" max="2000" step="1" value="100" class="css-test-led-input">
|
||||
@@ -62,6 +62,17 @@
|
||||
<button class="btn btn-icon btn-sm btn-secondary css-test-led-apply" onclick="applyCssTestSettings()" title="Apply" data-i18n-title="color_strip.test.apply">✓</button>
|
||||
</div>
|
||||
|
||||
<!-- FPS chart (for api_input sources) — matches target card sparkline -->
|
||||
<div id="css-test-fps-chart-group" class="target-fps-row" style="display:none">
|
||||
<div class="target-fps-sparkline">
|
||||
<canvas id="css-test-fps-chart"></canvas>
|
||||
</div>
|
||||
<div class="target-fps-label">
|
||||
<span id="css-test-fps-value" class="metric-value">0</span>
|
||||
<span class="target-fps-avg" id="css-test-fps-avg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="css-test-status" class="css-test-status" data-i18n="color_strip.test.connecting">Connecting...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user