feat(settings): server global brightness and Wi-Fi driver resync

- Serve GET /settings as JSON by removing duplicate HTML route (use /settings/page for the standalone UI).

- Save global_brightness via PUT; broadcast to connected drivers; push saved level when outbound WS connects.

- Zones UI loads brightness from GET /settings only (no localStorage).

- Bump led-driver submodule for settings.save on brightness with save flag.

- Extend API doc and endpoint tests for global_brightness.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pi
2026-05-03 22:15:30 +12:00
parent 3cca0cffc5
commit 827eb97203
8 changed files with 132 additions and 48 deletions

View File

@@ -2,32 +2,12 @@
let currentZoneId = null;
let brightnessSendTimeout = null;
const UI_BRIGHTNESS_STORAGE_KEY = "led_controller_ui_brightness";
function clamp255(n) {
const v = parseInt(n, 10);
if (Number.isNaN(v)) return null;
return Math.max(0, Math.min(255, v));
}
function loadSavedUiBrightness() {
try {
const raw = localStorage.getItem(UI_BRIGHTNESS_STORAGE_KEY);
if (raw == null) return null;
return clamp255(raw);
} catch (_) {
return null;
}
}
function persistUiBrightness(value) {
const v = clamp255(value);
if (v === null) return;
try {
localStorage.setItem(UI_BRIGHTNESS_STORAGE_KEY, String(v));
} catch (_) {}
}
function applyBrightnessSliders(val) {
const v = clamp255(val);
if (v === null) return;
@@ -37,9 +17,25 @@ function applyBrightnessSliders(val) {
if (menuSlider) menuSlider.value = String(v);
}
async function saveGlobalBrightnessToServer(val) {
try {
const res = await fetch("/settings/settings", {
method: "PUT",
headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "same-origin",
body: JSON.stringify({ global_brightness: val }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
console.warn("global_brightness save failed:", err.error || res.status);
}
} catch (e) {
console.warn("global_brightness save failed:", e);
}
}
function sendZoneBrightness(value) {
const val = Math.max(0, Math.min(255, parseInt(value, 10) || 0));
persistUiBrightness(val);
const headerSlider = document.getElementById('header-brightness-slider');
const menuSlider = document.getElementById('menu-brightness-slider');
if (headerSlider && String(headerSlider.value) !== String(val)) {
@@ -54,6 +50,7 @@ function sendZoneBrightness(value) {
brightnessSendTimeout = setTimeout(() => {
(async () => {
try {
await saveGlobalBrightnessToServer(val);
const section = document.querySelector('.presets-section[data-zone-id]');
const names = typeof window.parseTabDeviceNames === 'function'
? window.parseTabDeviceNames(section)
@@ -1027,22 +1024,41 @@ document.addEventListener('DOMContentLoaded', () => {
const menuBrightnessSlider = document.getElementById('menu-brightness-slider');
const headerBrightnessSlider = document.getElementById('header-brightness-slider');
const savedBr = loadSavedUiBrightness();
if (savedBr !== null) {
applyBrightnessSliders(savedBr);
}
if (menuBrightnessSlider) {
menuBrightnessSlider.addEventListener('input', (e) => {
sendZoneBrightness(e.target.value);
});
}
if (headerBrightnessSlider) {
headerBrightnessSlider.addEventListener('input', (e) => {
sendZoneBrightness(e.target.value);
});
// Apply saved (or default) level to devices once the page is ready.
sendZoneBrightness(headerBrightnessSlider.value);
}
(async () => {
let fromServer = null;
try {
const res = await fetch('/settings', {
headers: { Accept: 'application/json' },
credentials: 'same-origin',
});
if (res.ok) {
const data = await res.json();
const g = data.global_brightness;
if (typeof g === 'number' && g >= 0 && g <= 255) {
fromServer = Math.round(g);
} else if (g != null && g !== '') {
const n = parseInt(String(g), 10);
if (!Number.isNaN(n) && n >= 0 && n <= 255) {
fromServer = n;
}
}
}
} catch (_) {}
if (fromServer !== null) {
applyBrightnessSliders(fromServer);
}
if (menuBrightnessSlider) {
menuBrightnessSlider.addEventListener('input', (e) => {
sendZoneBrightness(e.target.value);
});
}
if (headerBrightnessSlider) {
headerBrightnessSlider.addEventListener('input', (e) => {
sendZoneBrightness(e.target.value);
});
sendZoneBrightness(headerBrightnessSlider.value);
}
})();
// When run/edit mode toggles, refresh tabs UI so edit actions show/hide immediately.
document.querySelectorAll('.ui-mode-toggle').forEach((btn) => {