From 2236ff308183f03e158939cf5da5423a28efe31c Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:30:51 -0400 Subject: [PATCH] fix(web-ui): MQTT password without TLS, Overview poll that never stopped, brightness slider error, token form left dirty (#745) * fix(web-ui): let the MQTT bridge form save a password without TLS PUT /api/v3/integrations/mqtt-bridge/config refuses a stored password while mqtt_tls is off unless allow_insecure_mqtt is set (the CWE-319 guard in api_v3/misc.py). The Tools tab form neither rendered a control for that flag nor sent it, so a password-protected broker on a LAN without TLS could never be saved from the UI, and once such a password was in bridge_config.json every later save from the form was refused. The form now shows "Allow without TLS (trusted network)" while "Use TLS" is unchecked, prefilled from the GET's config.allow_insecure_mqtt, and mqttBody() sends its state as allow_insecure_mqtt. The box is off until the user ticks it, so the server's guard still refuses a cleartext password by default. Tests: the Tools DOM suite checks the control, its show/hide with the TLS box, the prefill and the value saved; a Flask test pins that the GET reports the opt-in (false until saved on). Co-Authored-By: Claude Opus 5.5 * fix(web-ui): stop the Overview reconciliation poll from running forever The reconciliation banner script in partials/overview.html re-asked /api/v3/plugins/reconciliation-status every 2 s until the answer said done, with no limit. The route answers done: false whenever ledmatrix_reconciliation.json is missing or unreadable, which happens when _run_startup_reconciliation raises before writing it or when /tmp is cleaned under a long-running web service (reconciliation runs once per process). The browser then sent that request every 2 s for as long as the page stayed open, on every tab, since the poll was never tied to the Overview being visible. The poll now gives up after 30 tries (a minute) and runs only while the Overview is the active, visible tab, registered with LEDVisibility under its own key like the other partials' pollers. Dismissing the banner ends it too. Test: test/js/unit/test_overview_reconciliation_poll.js runs the shipped script in a vm with fake timers and fetch. Co-Authored-By: Claude Opus 5.5 * fix(web-ui): drop the Display tab's lookup of a removed brightness label The brightness slider's input handler in partials/display.html set the text of both #brightness-value and #brightness-display. #387 (978a03b42) removed the "LED brightness: N%" line that carried #brightness-display, so getElementById returned null and every step of the slider threw "Cannot set properties of null" into the console. The visible label still updated, because it is written first. The dead lookup is removed. Test: test/js/unit/test_display_partial_ids.js checks every literal getElementById() in the partial's inline scripts against the ids its markup renders, and runs the shipped script in a vm to move the slider. Co-Authored-By: Claude Opus 5.5 * fix(web-ui): a created API token leaves the General tab's form clean app.js marks a form data-dirty on any input inside it and removes the mark only after a successful htmx request; its beforeunload handler asks "Leave site?" while a visible form is still dirty. The API token form in partials/general.html posts through window.webLogin.createToken with fetch, so the mark survived the token being created and a reload of the page with the General tab open prompted about a change that had already been saved. createToken now removes data-dirty after a successful create, next to the form.reset() it already did. A refused request keeps the mark. Test: test/js/unit/test_general_web_login_token.js runs the shipped script in a vm with a fake fetch and DOM. Co-Authored-By: Claude Opus 5.5 * test(js): match