mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-21 20:33:33 +00:00
85a8030ee92f51b874ee7c45d2cf337d16843b4b
51 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
85a8030ee9 |
fix(template): apply |int filter to rp1_rio comparisons in display.html
Without |int, a string-typed value (e.g. from a hand-edited config.json) causes both selected tests to fail and the select renders with no option pre-selected. Matches the existing pattern used for multiplexing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f6e9c7688d |
fix(pi5): correct gpio_slowdown guidance — Pi 5 PIO default is 1, not 4-5
The upstream library defaults gpio_slowdown to 1 for Pi 5 (IsPi4() ? 2 : 1). In PIO mode the value is a pixel-clock divisor, so 4-5 was unnecessarily conservative advice. Updated help text and error log to reflect the actual range (1-3 typical for Pi 5 PIO; inverted effect in RIO mode). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c48c06ffa7 |
feat(pi5): expose RP1 backend selector, fix gpio defaults, surface init failures in web UI
- Add rp1_rio select (PIO/RIO) to Display Settings hardware config section; saved via /api/v3/config/main with 0-or-1 validation — previously the key existed in config.json but was not editable from the UI - Update gpio_slowdown help text with per-model guidance (Pi 3: 3, Pi 4: 4, Pi 5: 4–5) and raise max from 5 → 10 to match full library range - Fix gpio_slowdown Python fallback default from 2 → 3 (only affects edge case where the runtime config section is absent; explicit config values are unchanged) - display_manager writes /tmp/led_matrix_hw_status.json at startup: ok/error; Display Settings page fetches it and shows a yellow warning banner when the matrix failed to initialize, including Pi 5 remediation steps - Add GET /api/v3/hardware/status endpoint that reads the status file - Improve fallback error log to include Pi 5 rebuild hint Pi 3/4 users: rp1_rio=0 is set in config but silently ignored by the library on non-RP1 hardware; all other changes are additive or tighten defaults only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
44d1a08db4 |
perf(plugins): dramatically speed up plugin manager tab load time (#333)
* fix(cache): check odds keys before generic live check in get_data_type_from_key Cache keys like odds_espn_basketball_nba_<id>_live contain both 'odds' and 'live'. The previous ordering matched the generic 'live' check first, returning 'sports_live' (30 s TTL) instead of the correct 'odds_live' (120 s TTL). This caused the ESPN odds API to be hit every 30 s per live game, frequently triggering the 3-second per-request timeout and returning no odds data. Moving the 'odds' check above the generic 'live' block restores the correct 120-second cache TTL for in-progress game odds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(display): use single-quoted HTML attributes for JSON hidden inputs Placing |tojson output (which contains double quotes) inside a double-quoted HTML attribute broke the attribute — browsers closed the attribute at the first inner quote, leaving JS with an empty or truncated value. JSON.parse then failed silently, leaving excluded=[] so all Vegas scroll plugins appeared checked (included) regardless of the actual excluded_plugins config. Switch to single-quoted HTML attributes so the JSON double quotes are valid inside the attribute value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * perf(plugins): dramatically speed up plugin manager tab load time ## Problem The Plugins tab loaded slowly and inconsistently (5–30s depending on cache state), with a blank spinner for the entire wait. Three root causes: 1. **N+1 subprocess per installed plugin** — `_get_local_git_info` ran 4 separate git subprocesses per plugin (rev-parse HEAD, abbrev-ref, config --get remote.origin.url, log --format=%cI). With 15 plugins that's 60 blocking subprocess spawns before the endpoint returned. 2. **Serial per-plugin loop** — the `/plugins/installed` endpoint processed each plugin sequentially: manifest read → git info → instance lookup → Vegas mode query, one plugin at a time. 3. **Serial JS loading** — the store search only started after installed plugins fully completed, so users waited for both round-trips back to back. No UI feedback during the wait. ## Changes ### Backend — src/plugin_system/store_manager.py - Consolidate 4 git subprocesses → 1: branch read from `.git/HEAD` (file I/O, no subprocess), remote URL parsed from `.git/config` (file I/O, no subprocess), SHA + commit date fetched together in a single `git log -1 --format=%H%n%cI` call - Existing signature-based cache already eliminates all subprocesses on warm hits; this change cuts cold-cache cost from 4 → 1 per plugin ### Backend — web_interface/blueprints/api_v3.py - Wrap per-plugin work in a `_build_plugin_entry()` helper and execute it across a `ThreadPoolExecutor(max_workers=8)` so all plugins are processed in parallel instead of sequentially - Fix double `get_plugin()` call per plugin (was called once for the enabled fallback and again for Vegas mode — now one shared call) ### Frontend — web_interface/static/v3/plugins_manager.js - Fire `searchPluginStore()` and `loadInstalledPlugins()` simultaneously instead of waiting for installed to complete before starting the store - After installed data arrives, call `applyStoreFiltersAndSort(true)` to refresh install/update/reinstall badges from already-cached store data (instant, no extra network call) ### Frontend — web_interface/templates/v3/partials/plugins.html - Add responsive skeleton cards to the installed plugins section that match real card proportions (removed automatically when data renders) - Replace the 5 featureless gray boxes in the store skeleton with 10 structured skeleton cards matching the real card layout ## Measured improvement on Pi 4 (11 installed plugins, ledpi-ticker) | Scenario | Before | After | |---|---|---| | Cold cache (first open) | ~8–15s | **0.9s** | | Warm cache (git cache hit) | ~1–2s | **55ms** | | UI feedback during load | blank spinner | skeleton cards | | Store waits for installed | yes (serial) | no (parallel) | Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(plugins): harden git metadata parsing and plugin entry building store_manager.py: - Detect worktree/submodule .git files (gitdir: <path>) and resolve to the actual git directory before reading HEAD or config - Wrap HEAD read_text in try/except OSError/NotADirectoryError so atypical repos return None instead of propagating exceptions - Guard config url line split with '=' presence check to avoid IndexError on malformed lines api_v3.py: - Wrap _build_plugin_entry body in a try/except via a thin outer wrapper so a single plugin's failure doesn't 500 the whole endpoint; failed entries return None and are filtered by the existing [r for r in results if r is not None] step - Narrow manifest except clause to FileNotFoundError, PermissionError, json.JSONDecodeError instead of bare Exception - Validate manifest is a dict before calling plugin_info.update() and log a debug message when it isn't Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
6a4644007d |
fix(display): Vegas excluded plugins always showing as checked (#332)
* fix(cache): check odds keys before generic live check in get_data_type_from_key Cache keys like odds_espn_basketball_nba_<id>_live contain both 'odds' and 'live'. The previous ordering matched the generic 'live' check first, returning 'sports_live' (30 s TTL) instead of the correct 'odds_live' (120 s TTL). This caused the ESPN odds API to be hit every 30 s per live game, frequently triggering the 3-second per-request timeout and returning no odds data. Moving the 'odds' check above the generic 'live' block restores the correct 120-second cache TTL for in-progress game odds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(display): use single-quoted HTML attributes for JSON hidden inputs Placing |tojson output (which contains double quotes) inside a double-quoted HTML attribute broke the attribute — browsers closed the attribute at the first inner quote, leaving JS with an empty or truncated value. JSON.parse then failed silently, leaving excluded=[] so all Vegas scroll plugins appeared checked (included) regardless of the actual excluded_plugins config. Switch to single-quoted HTML attributes so the JSON double quotes are valid inside the attribute value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1c4d5c5271 |
feat(sync): multi-display wireless sync — extend scrolling across two LED matrices (#330)
* feat(sync): multi-display wireless sync — extend scrolling across two LED matrices Adds a leader/follower sync system that extends Vegas scroll mode content continuously across two physically adjacent LED matrix units over WiFi. Architecture: - Leader broadcasts scroll position via UDP at ~90fps; follower renders the offset slice of the same image at 60fps using dead reckoning to absorb UDP jitter (smooth, stutter-free motion) - At each cycle transition the leader sends the composed scroll image via TCP (PNG-compressed ~15–40KB) so both displays render pixel-identical content regardless of plugin data timing differences - Auto-discovery via UDP subnet broadcast — no IP configuration required - Heartbeat watchdog (6s timeout) falls back to standalone if peer goes offline Key files: - src/common/sync_manager.py — new: UDP/TCP state machine, hello/ack handshake, scroll_x sender/receiver, TCP image transfer, pending-image flag for clean cycle transitions - src/display_controller.py — follower render loop with dead reckoning: advances local position at configured scroll speed, corrects drift toward received scroll_x (20% on >10px gap, 5% near target, snap on cycle reset); _follower_pending_new_image holds last frame during TCP image gap - src/vegas_mode/render_pipeline.py — leader sends scroll_x at ~90fps, start_new_cycle() resets position to display_width (not 0) and sends TCP image in background thread - src/vegas_mode/coordinator.py — set_sync_manager() / set_update_callback() wiring; defers hot-swap recompose while sync is active - web_interface/blueprints/api_v3.py — sync config save endpoint, GET /api/v3/sync/status for live status polling - web_interface/templates/v3/partials/display.html — Multi-Display Sync section: role selector (Standalone/Leader/Follower), position (Left/Right of leader, follower only), UDP port, live status indicator - config/config.template.json — sync block: role, port, follower_position Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): address PR review findings - sync_manager: replace Optional[callable] with proper Callable types from typing; tighten set_on_new_cycle/set_on_scroll_image/set_on_follower_connected signatures to match their actual callback signatures - sync_manager: log a one-shot warning when send_frame produces a packet exceeding the 65000-byte UDP cap instead of silently dropping it - display_controller: correct stale comment in _send_follower_frame (was "30fps / PNG encode/decode"; actual behavior is ~90fps raw RGB) - display.html: guard setInterval with window.syncStatusInterval to prevent duplicate pollers if the script runs more than once - display.html: replace innerHTML with DOM node creation + textContent for status icon/text to avoid inserting API-derived values via innerHTML Skip: time.time() → monotonic and self.config staleness are pre-existing issues not introduced by this PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): address second round of PR review findings - sync_manager: guard TCP image receive against OOM — validate length against 10 MB cap before allocating; log and close on invalid length - display_controller: _follower_gated_update now allows update_display() through when the leader is offline (is_follower_active() == False) so the display recovers normally when falling back to standalone mode - coordinator: normalize a standalone SyncManager to None in set_sync_manager() so the render pipeline never treats a no-op manager as an active one - coordinator: derive _UPDATE_TICK_FRAMES from target_fps * 4 instead of the hardcoded 500 so the ~4s cadence holds at any configured FPS - render_pipeline: replace bare except/pass on blank-frame push with logger.exception() so failures are visible in logs Skip: config.template.json comments — JSON does not support inline comments. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): address third round of PR review findings - sync_manager: use 'with socket.socket(...)' in send_scroll_image so the TCP socket is always closed even if connect/sendall raises - sync_manager: add _scroll_image_lock to serialize all reads/writes to _on_scroll_image and _pending_scroll_image between _image_server_loop and set_on_scroll_image, eliminating the lost-delivery race; callback is invoked outside the lock to avoid holding it during user code - sync_manager: validate scroll image dimensions (max 100000×256) and catch DecompressionBombError before img.load() in _image_server_loop - sync_manager: log socket close exceptions at debug level in stop() instead of silently passing - sync_manager: replace hardcoded /tmp/ with tempfile.gettempdir() for STATUS_FILE (atomic write was already in place) - sync_manager: check _RAW_MAGIC first in _follower_recv_loop routing so magic-tagged frames are always identified correctly regardless of size Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): address fourth round of PR review findings - sync_manager: log INCOMPATIBLE error only on state transition (guard with prev_state != LeaderState.INCOMPATIBLE) so repeated hello packets from an incompatible follower don't spam the log - sync_manager: replace O(n²) bytes concatenation in TCP image receive loop with bytearray + extend() for linear-time accumulation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): suppress Codacy false positives - display_controller: rename local var 'sh' to 'scroll_h' so Codacy's pattern matcher doesn't confuse it with the 'sh' shell library - sync_manager: add '# nosec B104' to all socket.bind("") calls — binding to all interfaces is intentional (UDP broadcast reception and TCP image server must accept connections from any local interface) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sync): add nosec B104 to socket creation lines for Codacy Codacy attributes the bind-to-all-interfaces finding to the socket.socket() creation lines (140, 439) rather than the .bind() calls. Added # nosec B104 there too so the suppression is seen at the line Codacy reports. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ceb4c4105f |
fix(wifi): reliable open AP with captive portal — tested on Trixie Pi (#320)
* fix(wifi): create truly open AP via nmcli connection add; add captive portal to nmcli path nmcli device wifi hotspot always attaches a WPA2 PSK on Bookworm/Trixie and silently ignores post-creation security modifications, causing users to be prompted for an unknown password. Switch to nmcli connection add with 802-11-wireless.mode ap and no security section — NM cannot auto-add a password to a profile that has no 802-11-wireless-security block. Also: - Remove dead DEFAULT_AP_PASSWORD / ap_password config field (stored but never passed to hostapd or nmcli, causing user confusion) - Add iptables port 80→5000 redirect to the nmcli AP path so captive portal auto-popup works on phones without hostapd (previously only worked on the hostapd path) - Clean up iptables rules on disable for the nmcli path - Improve LED message on AP enable: show SSID, "No password", and IP:port on both paths so users know exactly how to connect - Fix systemd template: replace hardcoded /home/ledpi/LEDMatrix/ with __PROJECT_ROOT_DIR__ placeholder (install script already writes correct path) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): address Codacy review findings in AP mode implementation - Validate ap_ssid/ap_channel from config before passing to subprocess (printable ASCII ≤32 chars; channel 1-14) to prevent command injection - Fix INPUT iptables rule: PREROUTING redirects port 80→5000 so the INPUT chain sees dport=5000, not 80. Old INPUT rule on port 80 was a no-op. - Refactor iptables setup/teardown into _setup_iptables_redirect() and _teardown_iptables_redirect() helpers, eliminating duplicate logic in the hostapd and nmcli paths - Save/restore ip_forward state (via /tmp/ledmatrix_ip_forward_saved) instead of forcing it to 0 on cleanup, which could break VPNs or bridges already relying on forwarding - nmcli path skips ip_forward management entirely: NM's ipv4.method=shared already manages it for the duration of the connection - Fix _get_ap_status_nmcli() verification: new 'connection add type wifi' profiles have type '802-11-wireless', not 'hotspot', so verification was always returning False. Now also matches by our known connection name. - Remove SSID-based connection deletion: deleting any profile whose SSID matched the AP SSID could destroy a user's saved home WiFi profile. Now only deletes by our application-managed profile names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(plugins): fix async race in refreshPlugins; use cache TTL to gate re-swap metadata fetch refreshPlugins() called searchPluginStore(true) and showNotification() immediately after refreshInstalledPlugins() without awaiting the returned Promise, so window.installedPlugins could still be stale when the store rendered its Installed/Reinstall badges. Chain .then() so both run only after the fetch completes. In initializePlugins(), the re-swap path always passed fetchCommitInfo=false to searchPluginStore, skipping GitHub metadata even when the 5-minute cache TTL had expired. Add storeCacheExpired() helper and compute isReswapWarm = _reswap && !storeCacheExpired() so fresh metadata is fetched whenever the cache is cold, regardless of whether the render is a first load or a tab re-swap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address three wifi_manager and one plugins_manager review findings wifi_manager.py: - _create_hostapd_config: use _validate_ap_config() for ssid/channel instead of raw self.config values; strip newlines from SSID to prevent config-file injection via the generated hostapd.conf - _setup_iptables_redirect: check return codes of sysctl ip_forward enable and both iptables -A calls; on any failure log the error output, call _teardown_iptables_redirect() to restore state, and return False instead of silently succeeding - _enable_ap_mode_nmcli_hotspot: on AP verification failure roll back fully — tear down iptables redirect, delete the LEDMatrix-Setup-AP connection profile, clear the LED message — before returning False plugins_manager.js: - initializePlugins: chain searchPluginStore(!isReswapWarm) inside loadInstalledPlugins().then() so window.installedPlugins is populated before the store renders Installed/Reinstall badges (same pattern applied to refreshPlugins() in the previous commit) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): use _find_command_path for iptables/sysctl; harden ip_forward save/restore Add _find_command_path() helper that extends _check_command()'s sbin-aware lookup to return the absolute binary path rather than a boolean. Use it in _setup_iptables_redirect and _teardown_iptables_redirect so iptables and sysctl are resolved via /sbin or /usr/sbin even when those directories are absent from PATH in systemd service environments. Also harden the ip_forward save/restore logic: - Read ip_forward from /proc/sys/net/ipv4/ip_forward (no subprocess, no PATH dependency) instead of spawning sysctl -n - Skip the sysctl -w ip_forward=1 write when the value is already "1" to avoid mutating state owned by another service (VPN, NM shared mode, bridge) - Track save success via presence of the save file: if the /proc read or file write fails, leave the file absent so teardown knows not to restore - In _teardown_iptables_redirect, only restore ip_forward when the save file exists; if absent, leave the current value untouched rather than forcing "0" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): check _setup_iptables_redirect return; fix hostapd LED SSID; teardown on exception - Both AP startup paths (hostapd and nmcli) now check the bool returned by _setup_iptables_redirect() and treat False as a hard failure: the hostapd path stops hostapd/dnsmasq and returns an error tuple; the nmcli path brings down and deletes the LEDMatrix-Setup-AP profile and clears the LED message - _enable_ap_mode_hostapd's LED message now calls _validate_ap_config() to get the same sanitized SSID that _create_hostapd_config() uses, so the displayed name always matches the AP actually broadcast by hostapd - _setup_iptables_redirect's outer except block now calls _teardown_iptables_redirect() before returning False so partial iptables/ ip_forward state is always cleaned up on unexpected exceptions; cleanup exceptions are caught and logged separately Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(wifi): add unit tests for AP mode — open network, iptables, LED, cleanup ordering Six pytest unit tests covering the five review scenarios. All subprocess and filesystem side-effects are mocked so the tests run without root, hardware, or a Pi OS environment. 1. test_nmcli_ap_profile_has_no_security_params — asserts the nmcli connection add command has no key-mgmt / psk / WPA arguments and sets mode=ap. 2. test_iptables_nat_rules_added_on_ap_start — verifies _setup_iptables_redirect emits a PREROUTING REDIRECT 80→5000 rule and an INPUT ACCEPT rule for port 5000 (not 80, which never hits INPUT after PREROUTING rewrites it). 3. test_iptables_rules_and_ip_forward_reverted_on_teardown — verifies the -D PREROUTING/-D INPUT calls and that sysctl restores the saved ip_forward value and removes the save file. 4. test_ip_forward_not_restored_when_save_file_absent — verifies teardown skips sysctl when the save file was never written, preventing blind ip_forward=0 on systems using ip_forward for VPNs or NM shared mode. 5. test_led_message_shows_ssid_no_password_and_url — asserts the LED message includes the SSID, 'No password', and the 192.168.4.1:5000 setup URL. 6. test_existing_ap_profiles_deleted_before_new_profile_created — asserts all known profile names are targeted for deletion before 'nmcli connection add'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(wifi): adopt adsb-feeder-image hotspot patterns — DNS spoofing, connectivity check, idle timeout, wrong-password UX, watchdog escalation Inspired by the production-proven approach in dirkhh/adsb-feeder-image. 1. DNS spoofing for automatic captive-portal popup (Change 1 — Critical) Write /etc/NetworkManager/dnsmasq-shared.d/ledmatrix-captive.conf with address=/#/192.168.4.1 before nmcli connection up so NM's built-in dnsmasq (ipv4.method=shared) resolves every hostname to the AP IP. This triggers the OS captive-portal popup automatically on iOS / Android / Windows / macOS — no manual navigation to 192.168.4.1:5000/setup required. New helpers: _write_nm_dnsmasq_captive_conf / _remove_nm_dnsmasq_captive_conf. New constants: NM_DNSMASQ_SHARED_DIR / NM_DNSMASQ_SHARED_CONF. 2. Real internet connectivity check (Change 2 — High) Add _check_internet_connectivity() (ping 8.8.8.8 + HTTP fallback). check_and_manage_ap_mode() now considers a device "disconnected" when nmcli shows connected but no real internet reachability, matching adsb-feeder's multi-method gateway/DNS/HTTP test approach. 3. AP idle timeout (Change 3 — Medium) Track _ap_enabled_at timestamp in enable_ap_mode(). Add _has_ap_clients() using 'iw dev <iface> station dump'. check_and_manage_ap_mode() auto-disables AP after ap_idle_timeout_minutes (default 15) with no associated clients. 4. Wrong-password error feedback (Change 4 — Medium) _connect_nmcli() detects "Secrets were required" / "authentication rejected" in nmcli stderr and prefixes the message with "wrong_password: ". The /api/v3/wifi/connect route propagates error_type="wrong_password" in the JSON response. captive_setup.html shows "Incorrect password — try again" (keeping the form active) instead of the generic failure message. 5. Escalating watchdog NM restart (Change 5 — Low) wifi_monitor_daemon.py tracks _consecutive_internet_failures. After _nm_restart_threshold (5) consecutive checks where nmcli shows connected but internet is unreachable, restart NetworkManager as a recovery step. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): restore safe AP-enable trigger; decouple internet check from AP logic The previous commit introduced _check_internet_connectivity() into check_and_manage_ap_mode(), which shared the same _disconnected_checks counter that triggers AP enable. This created a false-positive risk: 90 seconds of packet loss on working WiFi would enable AP mode and kick off the connection. Fix: restore nmcli association state as the sole AP-enable trigger (original, safe behaviour). The internet connectivity check is now used only in the daemon watchdog for the NM-restart escalation — matching how adsb-feeder-image actually structures the two concerns (initial setup detection vs. ongoing monitoring). Also clarify daemon comment: the connectivity check runs once per cycle in the watchdog block, not inside check_and_manage_ap_mode, so there is no double-call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): remove PMF setting from open AP profile — breaks nmcli connection add on Trixie NM 1.52+ 802-11-wireless-security.pmf is only valid within a security section that also includes key-mgmt. Adding it to an open-network profile causes NM 1.52+ to reject the connection add with 'key-mgmt: property is missing'. PMF has no meaning for open APs (it only applies to WPA2/WPA3), so the setting is simply removed rather than worked around. Found by testing on devpi (Trixie, NM 1.52.1). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): add nftables fallback for port redirect; graceful degradation when neither available Tested on devpi (Trixie, NM 1.52.1): iptables is not installed; nftables is. The original code called _setup_iptables_redirect() and treated 'iptables not found' as a hard failure, rolling back the entire AP setup. Changes: - _setup_iptables_redirect() now tries iptables first, then nftables as a fallback. When neither is available it logs a warning and returns True so the AP still comes up (DNS spoofing still triggers the captive portal popup; users land on port 5000 directly instead of being auto-redirected from 80). - Split into _setup_iptables_redirect_iptables() and _setup_iptables_redirect_nftables() for clarity. - Added _redirect_backend instance var ("iptables" | "nftables" | None) so _teardown_iptables_redirect() uses the same tool that setup used. - nftables teardown: deletes the 'ledmatrix' table (clean, no leftover rules). - iptables teardown: unchanged logic (ip_forward save/restore). - Also removed the PMF workaround for Trixie: 802-11-wireless-security.pmf requires key-mgmt to also be set, breaking open-network creation on NM 1.52+. Open APs have no management frame protection by definition. - Update teardown test to set _redirect_backend = "iptables" before calling it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(wifi): public check_internet_connectivity(); absolute systemctl path; stricter mode assertion wifi_manager.py: - Add public check_internet_connectivity() wrapping the private method so the daemon does not reach into the private API wifi_monitor_daemon.py: - Call wifi_manager.check_internet_connectivity() instead of the private _check_internet_connectivity() - Use /usr/bin/systemctl (absolute path) instead of bare "systemctl" - Wrap NM restart in try/except with check=True; only reset _consecutive_internet_failures on success — on CalledProcessError or other exception, log the error and leave the counter unchanged so the next cycle retries test/test_wifi_manager_ap.py: - Replace loose `assert "ap" in add_calls[0]` (list-membership check that could be satisfied by any element equal to "ap") with an explicit key/value check: locate "802-11-wireless.mode" in the command list and assert the next element is exactly "ap" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
acaf8a248e |
feat(web): update-available banner in web UI (#311)
* feat(web): add update-available banner to web UI Adds a polite, dismissible banner between the header and navigation tabs that appears when the local repo is behind origin/main. Shows commit count and a one-click "Update Now" button that triggers the existing git_pull action. - New GET /api/v3/system/check-update endpoint (5-min cache, compares local HEAD vs origin/main SHA) - Banner auto-checks on page load then every 30 minutes - Dismiss persists for the browser session via sessionStorage - Styled for both light and dark themes - Cache invalidated after successful git_pull so banner hides immediately Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(update-banner): address review findings — lock, returncode checks, update_available logic, a11y, button state - Add _update_check_lock (threading.Lock) around all reads/writes to _update_check_cache in check_for_update() and git_pull, preventing races on concurrent requests - Validate returncode for git fetch, rev-parse HEAD, and rev-parse origin/main; raise RuntimeError on failure so errors are caught and returned as error payloads instead of silently producing stale/empty SHAs - Set update_available = commits_behind > 0 (was unconditionally True when local_sha != remote_sha); prevents false positive when local is ahead of remote - Add type="button" and aria-label="Dismiss update" to the icon-only dismiss button - Restore btn.innerHTML and btn.disabled in both success and error paths of applyUpdate(); only hide the banner and clear sessionStorage when data.status === 'success' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(update-banner): address second-round review findings api_v3.py: - Move all git work inside _update_check_lock so concurrent requests re-check cache staleness after acquiring the lock; only the first caller runs git fetch/rev-parse/log, subsequent callers return the cached result - Check log_result.returncode and raise on failure so a broken git log doesn't produce a silent false-negative (commits_behind=0) - Rename loop variable l → commit_line base.html: - Replace boolean _dismissed flag with SHA-scoped sessionStorage key 'update-sha-dismissed'; dismissing for SHA X still allows the banner to reappear when origin/main advances to SHA Y - Successful applyUpdate clears 'update-sha-dismissed' so the next update cycle can show the banner again - Add aria-live="polite" aria-atomic="true" to #update-banner-text so screen readers announce content changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
4ef3f8cad5 |
feat(web): add config backup & restore UI (#310)
* feat(web): add config backup & restore UI Adds a Backup & Restore tab to the v3 web UI that packages user config, secrets, WiFi, user-uploaded fonts, plugin image uploads, and the installed plugin list into a single ZIP for safe reinstall recovery. Restore extracts the bundle, snapshots current state via the existing atomic config manager (so rollback stays available), reapplies the selected sections, and optionally reinstalls missing plugins from the store. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(backup): address PR review findings - backup_manager: read plugin state from "states" key (not "plugins") to match the actual plugin_state.json format written by state_manager - backup_manager: stream ZIP directly to a temp file instead of building it in an io.BytesIO buffer to avoid OOM on Raspberry Pi - backup_manager: tighten plugin-uploads path validation in validate_backup and restore_backup to require "/uploads/" in the path, rejecting any non-uploads files smuggled under assets/plugins/ - api_v3: enforce 200 MB upload limit by streaming in chunks rather than relying on validate_file_upload (which only checks the filename) - api_v3: replace bool() with _coerce_to_bool() for RestoreOptions fields so string "false" is not treated as truthy - api_v3: capture and log _save_config_atomic return value instead of discarding it; log rather than silence font-cache and config-reload errors - backup_restore.html: track inspectedFile so runRestore always applies to the file the user inspected, not a subsequently selected file; clear on input change or clearRestore() - backup_restore.html: throw on non-success restore payload so errors are surfaced via the error notification path instead of yellow "warnings" - test: update fixture to use correct "states" key structure; import SCHEMA_VERSION constant instead of hardcoding 1; rename unused err -> _err Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(backup): address second round of PR review findings - api_v3: guard opts_dict with isinstance check after json.loads so a non-object JSON payload (null, array, etc.) returns a 400 instead of a 500 AttributeError - backup_manager: wrap tmp ZIP creation and os.replace in try/except so the .zip.tmp temp file is always removed on any failure - backup_manager: replace hardcoded Path("/tmp/_zip_check") sentinel in validate_backup with a proper tempfile.TemporaryDirectory() so path traversal checks are portable and leave no artifacts - backup_restore.html: detect partial-success responses (plugins_failed or errors non-empty) even when status is 'success' and render yellow/warning styling and notify instead of green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(backup): add post-install steps for restored plugins; conditional restart hint - api_v3: after a successful plugin reinstall during restore, run the same post-install sequence used by the normal /plugins/install flow: invalidate schema cache, discover_plugins()/load_plugin(), and set_plugin_installed() so restored plugins are immediately available - backup_restore.html: only show the "restart the display service" hint when at least one item was restored or at least one plugin was installed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(backup): address Codacy findings - api_v3: replace 'fonts' in ' '.join(result.restored) substring check with any(r.startswith("fonts") for r in result.restored) to avoid fragile joined-string membership testing - api_v3: replace deprecated datetime.utcnow() and utcfromtimestamp() with datetime.now(timezone.utc) and fromtimestamp(..., timezone.utc); add timezone to import - test: remove unused import io (backup_manager no longer uses BytesIO) - src/backup_manager.py hardcoded /tmp sentinel was already fixed in a prior commit (tempfile.TemporaryDirectory) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
941291561a |
fix(web): expose GitHub install handlers, simplify Alpine loader, explicit Flask threading (#305)
A user reported that buttons in the v3 web UI were unresponsive in Safari after a fresh install. The screenshots showed Alpine.js actually running fine end-to-end — the real issues are a narrow handler-exposure bug and some latent brittleness worth cleaning up at the same time. plugins_manager.js: attachInstallButtonHandler and setupGitHubInstallHandlers were declared inside the main IIFE, but the typeof guards that tried to expose them on window ran *outside* the IIFE, so typeof always evaluated to 'undefined' and the assignments were silently skipped. The GitHub "Install from URL" button therefore had no click handler and the console printed [FALLBACK] attachInstallButtonHandler not available on window on every load. Fixed by assigning window.attachInstallButtonHandler and window.setupGitHubInstallHandlers *inside* the IIFE just before it closes, and removing the dead outside-the-IIFE guards. base.html: the Alpine.js loader was a 50-line dynamic-script + deferLoadingAlpine + isAPMode branching block. script.defer = true on a dynamically-inserted <script> is a no-op (dynamic scripts are always async), the deferLoadingAlpine wrapper was cargo-culted, and the AP-mode branching reached out to unpkg unnecessarily on LAN installs even though alpinejs.min.js already ships in web_interface/static/v3/js/. Replaced with a single <script defer src="..."> tag pointing at the local file plus a small window-load rescue that only pulls the CDN copy if window.Alpine is still undefined. start.py / app.py: app.run() has defaulted to threaded=True since Flask 1.0 so this is not a behavior change, but the two long-lived /api/v3/stream/* SSE endpoints would starve every other request under a single-threaded server. Setting threaded=True explicitly makes the intent self-documenting and guards against future regressions. Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
781224591f |
fix: post-audit follow-up code fixes (cache, fonts, icons, dev script) (#307)
* fix: post-audit follow-up code fixes (cache, fonts, icons, dev script, CI) The docs refresh effort (#306, ledmatrix-plugins#92) surfaced seven code bugs that were intentionally left out of the docs PRs because they required code changes rather than doc fixes. This PR addresses the six that belong in LEDMatrix (the seventh — a lacrosse-scoreboard mode rename — lives in the plugins repo). Bug 1: cache_manager.delete() AttributeError src/common/api_helper.py:287 and src/plugin_system/resource_monitor.py:343 both call cache_manager.delete(key), which doesn't exist — only clear_cache(key=None). Added a delete() alias method on CacheManager that forwards to clear_cache(key). Reverts the "There is no delete() method" wording in DEVELOPER_QUICK_REFERENCE, .cursorrules so the docs match the new shim. Bug 2: dev_plugin_setup.sh PROJECT_ROOT resolution scripts/dev/dev_plugin_setup.sh:9 set PROJECT_ROOT to SCRIPT_DIR instead of walking up two levels to the repo root, so PLUGINS_DIR resolved to scripts/dev/plugins/ and created symlinks under the script's own directory. Fixed the path and removed the stray scripts/dev/plugins/of-the-day symlink left by earlier runs. Bug 3: plugin custom icons regressed from v2 to v3 web_interface/blueprints/api_v3.py built the /plugins/installed response without including the manifest's "icon" field, and web_interface/templates/v3/base.html hardcoded fas fa-puzzle-piece in all three plugin-tab render sites. Pass the icon through the API and read it from the templates with a puzzle-piece fallback. Reverts the "currently broken" banners in docs/PLUGIN_CUSTOM_ICONS.md and docs/PLUGIN_CUSTOM_ICONS_FEATURE.md. Bug 4: register_plugin_fonts was never wired up src/font_manager.py:150 defines register_plugin_fonts(plugin_id, font_manifest) but nothing called it, so plugin manifests with a "fonts" block were silently no-ops. Wired the call into PluginManager.load_plugin() right after plugin_loader.load_plugin returns. Reverts the "not currently wired" warning in docs/FONT_MANAGER.md's "For Plugin Developers" section. Bug 5: dead web_interface_v2 import pattern (LEDMatrix half) src/base_odds_manager.py had a try/except importing web_interface_v2.increment_api_counter, falling back to a no-op stub. The module doesn't exist anywhere in the v3 codebase and no API metrics dashboard reads it. Deleted the import block and the single call site; the plugins-repo half of this cleanup lands in ledmatrix-plugins#<next>. Bug 7: no CI test workflow .github/workflows/ only contained security-audit.yml; pytest ran locally but was not gated on PRs. Added .github/workflows/tests.yml running pytest against Python 3.10, 3.11, 3.12 in EMULATOR=true mode, skipping tests marked hardware or slow. Updated docs/HOW_TO_RUN_TESTS.md to reflect that the workflow now exists. Verification done locally: - CacheManager.delete(key) round-trips with set/get - base_odds_manager imports without the v2 module present - dev_plugin_setup.sh PROJECT_ROOT resolves to repo root - api_v3 and plugin_manager compile clean - tests.yml YAML parses - Script syntax check on dev_plugin_setup.sh Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review comments on #307 - src/cache_manager.py: clear_cache(key) treated empty string as "wipe all" because of `if key:`. Switched to `key is None` branching, made delete(key) and clear_cache(key) reject empty strings and None outright with ValueError, and updated both docstrings to make the contract explicit. Verified locally with a round-trip test that clear_cache() (no arg) still wipes everything but clear_cache("") and delete("") raise. - src/plugin_system/plugin_manager.py: was reaching for the font manager via getattr(self.display_manager, 'font_manager', None). PluginManager already takes a dedicated font_manager parameter (line 54) and stores it as self.font_manager (line 69), so the old path was both wrong and could miss the font manager entirely when the host injects them separately. Switched to self.font_manager directly with the same try/except warning behavior. - web_interface/templates/v3/base.html: in the full plugin-tab renderer, the icon was injected with `<i class="${escapeHtml(plugin.icon)}">` — but escapeHtml only escapes <, >, and &, not double quotes, so a manifest with a quote in its icon string could break out of the class attribute. Replaced the innerHTML template with createElement for the <i> tag, set className from plugin.icon directly (no string interpolation), and used a text node for the label. Same fix shape would also harden the two stub-renderer sites at line 515 / 774, but those already escape `"` to " and CodeRabbit only flagged this site, so leaving them for now. - docs/FONT_MANAGER.md: clarified that the Manual Font Overrides *workflow* (set_override / remove_override / font_overrides.json) is the supported override path today, and only the Fonts tab in the web UI is the placeholder. Previous wording conflated the two and made it sound like overrides themselves were broken. - docs/HOW_TO_RUN_TESTS.md: replaced the vague "see the PR adding it" with a concrete link to #307 and a note that the workflow file itself is held back pending the workflow scope. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
5ea2acd897 |
fix(web): array-table Add Item button creates rows with input fields (#302) (#303)
The data-item-properties attribute on the Add Item button was serialized
inside double-quoted HTML using {{ item_properties|tojson|e }}. Jinja2's
|tojson returns Markup (marked safe), making |e a no-op — the JSON
double quotes were not escaped to ". The browser truncated the
attribute at the first " in the JSON, so addArrayTableRow() parsed an
empty object and created rows with only a trash icon.
Fix: switch to single-quote attribute delimiters (JSON only uses double
quotes internally) and filter item_properties to only the display
columns, avoiding large nested objects in the attribute value.
Closes #302
Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
68a0fe1182 |
fix(web): resolve plugin settings tabs not loading (#301)
* fix(web): resolve plugin settings tabs not loading due to enhancement race Two co-occurring bugs prevented plugin setting tabs from loading: 1. Both stub-to-full app() enhancement paths (tryEnhance and requestAnimationFrame) could fire independently, with the second overwriting installedPlugins back to [] after init() already fetched them. Added a guard flag (_appEnhanced) and runtime state preservation to prevent this race. 2. Plugin config x-init only loaded content if window.htmx was available at that exact moment, with no retry or fallback. Added retry loop (up to 3s) and fetch() fallback for resilience. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): use runtime default tab and add Alpine.initTree to fetch fallback - Replace hard-coded 'overview' comparison with runtime defaultTab (isAPMode ? 'wifi' : 'overview') in both enhancement paths, so activeTab is preserved correctly in AP mode - Add Alpine.initTree(el) call in the plugin config fetch() fallback so Alpine directives in the injected HTML are initialized, matching the pattern used by loadOverviewDirect and loadWifiDirect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
7afc2c0670 |
fix(web): increase chain_length max from 8 to 32 (#300)
* fix(web): increase chain_length max from 8 to 32 The web UI form input capped chain_length at 8 panels, preventing users with larger displays (e.g. 16-panel setups) from configuring their hardware through the UI. The backend API had no such limit. Changed max="8" to max="32" to support large display configurations. Added panel count example to the help text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): add server-side bounds validation for display hardware fields The API endpoint at /api/v3/config/main accepted any integer value for display hardware fields (chain_length, rows, cols, brightness, etc.) without bounds checking. Only the HTML form had min/max attributes, which are trivially bypassed by direct API calls. Added _int_field_limits dict with bounds for all integer hardware fields: chain_length: 1-32, parallel: 1-4, brightness: 1-100, rows: 8-128, cols: 16-128, scan_mode: 0-1, pwm_bits: 1-11, pwm_dither_bits: 0-2, pwm_lsb_nanoseconds: 50-500, limit_refresh_rate_hz: 0-1000, gpio_slowdown: 0-5 Out-of-bounds or non-integer values now return 400 with a clear error message (e.g. "Invalid chain_length value 99. Must be between 1 and 32.") before any config is persisted. Follows the same inline validation pattern already used for led_rgb_sequence, panel_type, and multiplexing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(api): strict int validation and add max_dynamic_duration_seconds bounds Reject bool/float types in _int_field_limits validation loop to prevent silent coercion, and add max_dynamic_duration_seconds to the validation map so it gets proper bounds checking instead of a raw int() call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
35df06b8e1 |
fix: resolve font upload "baseUrl is not defined" error (#235) (#297)
The baseUrl variable was declared inside an IIFE that skips re-execution on HTMX reloads, so it became undefined when the fonts tab was reloaded. Since baseUrl was just window.location.origin prepended to absolute paths like /api/v3/fonts/upload, it was unnecessary — fetch() with a leading slash already resolves against the current origin. Remove baseUrl entirely and use relative URLs in all 7 fetch calls. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
77e9eba294 |
fix: overhaul WiFi captive portal for reliable setup (#296)
* fix: overhaul WiFi captive portal for reliable device detection and fast setup The captive portal detection endpoints were returning "success" responses that told every OS (iOS, Android, Windows, Firefox) that internet was working — so the portal popup never appeared. This fixes the core issue and improves the full setup flow: - Return portal-triggering redirects when AP mode is active; normal success responses when not (no false popups on connected devices) - Add lightweight self-contained setup page (9KB, no frameworks) for the captive portal webview instead of the full UI - Cache AP mode check with 5s TTL (single systemctl call vs full WiFiManager instantiation per request) - Stop disabling AP mode during WiFi scans (which disconnected users); serve cached/pre-scanned results instead - Pre-scan networks before enabling AP mode so captive portal has results immediately - Use dnsmasq.d drop-in config instead of overwriting /etc/dnsmasq.conf (preserves Pi-hole and other services) - Fix manual SSID input bug that incorrectly overwrote dropdown selection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review findings for WiFi captive portal - Remove orphaned comment left over from old scan_networks() finally block - Add sudoers rules for dnsmasq drop-in copy/remove to install script - Combine cached-network message into single showMsg call (was overwriting) - Return (networks, was_cached) tuple from scan_networks() so API endpoint derives cached flag from the scan itself instead of a redundant AP check - Narrow exception catch in AP mode cache to SubprocessError/OSError and log the failure for remote debugging - Bound checkNewIP retries to 20 attempts (60s) before showing fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2c2fca2219 |
fix(web): use HTMX for Plugin Manager tab loading (#294)
* fix: auto-repair missing plugins and graceful config fallback Plugins whose directories are missing (failed update, migration, etc.) now get automatically reinstalled from the store on startup. The config endpoint no longer returns a hard 500 when a schema is unavailable — it falls back to conservative key-name-based masking so the settings page stays functional. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: handle ledmatrix- prefix in plugin updates and reconciliation The store registry uses unprefixed IDs (e.g., 'weather') while older installs used prefixed config keys (e.g., 'ledmatrix-weather'). Both update_plugin() and auto-repair now try the unprefixed ID as a fallback when the prefixed one isn't found in the registry. Also filters system config keys (schedule, display, etc.) from reconciliation to avoid false positives. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address code review findings for plugin auto-repair - Move backup-folder filter from _get_config_state to _get_disk_state where the artifact actually lives - Run startup reconciliation in a background thread so requests aren't blocked by plugin reinstallation - Set _reconciliation_done only after success so failures allow retries - Replace print() with proper logger in reconciliation - Wrap load_schema in try/except so exceptions fall through to conservative masking instead of 500 - Handle list values in _conservative_mask_config for nested secrets - Remove duplicate import re Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add thread-safe locking to PluginManager and fix reconciliation retry PluginManager thread safety: - Add RLock protecting plugin_manifests and plugin_directories - Build scan results locally in _scan_directory_for_plugins, then update shared state under lock - Protect reads in get_plugin_info, get_all_plugin_info, get_plugin_directory, get_plugin_display_modes, find_plugin_for_mode - Protect manifest mutation in reload_plugin - Prevents races between background reconciliation thread and request handlers reading plugin state Reconciliation retry: - Clear _reconciliation_started on exception so next request retries - Check result.reconciliation_successful before marking done - Reset _reconciliation_started on non-success results to allow retry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): use HTMX for Plugin Manager tab loading instead of custom fetch The Plugin Manager tab was the only tab using a custom window.loadPluginsTab() function with plain fetch() instead of HTMX. This caused a race condition where plugins_manager.js listened for htmx:afterSwap to initialize, but that event never fired for the custom fetch. Users had to navigate to a plugin config tab and back to trigger initialization. Changes: - Switch plugins tab to hx-get/hx-trigger="revealed" matching all other tabs - Remove ~560 lines of dead code (script extraction for a partial with no scripts, nested retry intervals, inline HTML card rendering fallbacks) - Add simple loadPluginsDirect() fallback for when HTMX fails to load - Remove typeof htmx guard on afterSwap listener so it registers unconditionally - Tighten afterSwap target check to avoid spurious re-init from other tab swaps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review findings across plugin system - plugin_manager.py: clear plugin_manifests/plugin_directories before update to prevent ghost entries for uninstalled plugins persisting across scans - state_reconciliation.py: remove 'enabled' key check that skipped legacy plugin configs, default to enabled=True matching PluginManager.load_plugin - app.py: add threading.Lock around reconciliation start guard to prevent race condition spawning duplicate threads; add -> None return annotation - store_manager.py: use resolved registry ID (alt_id) instead of original plugin_id when reinstalling during monorepo migration - base.html: check Response.ok in loadPluginsDirect fallback; trigger fallback on tab click when HTMX unavailable; remove active-tab check from 5-second timeout so content preloads regardless Skipped: api_v3.py secret redaction suggestion — the caller at line 2539 already tries schema-based mask_secret_fields() before falling back to _conservative_mask_config, making the suggested change redundant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip backup dirs in plugin discovery and fix HTMX event syntax - plugin_manager.py: skip directories containing '.standalone-backup-' during discovery scan, matching state_reconciliation.py behavior and preventing backup manifests from overwriting live plugin entries - base.html: fix hx-on::htmx:response-error → hx-on::response-error (the :: shorthand already adds the htmx: prefix, so the original syntax resolved to htmx:htmx:response-error making the handler dead) Skipped findings: - web-ui-info in _SYSTEM_CONFIG_KEYS: it's a real plugin with manifest.json and config entry, not a system key - store_manager config key migration: valid feature request for handling ledmatrix- prefix rename, but new functionality outside this PR scope Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): add fetch timeout to loadPluginsDirect fallback Add AbortController with 10s timeout so a hanging fetch doesn't leave data-loaded set and block retries. Timer is cleared in both success and error paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
81a022dbe8 |
fix(web): resolve file upload config lookup for server-rendered forms (#279)
* fix(web): resolve file upload config lookup for server-rendered forms
The file upload widget's getUploadConfig() function failed to map
server-rendered field IDs (e.g., "static-image-images") back to schema
property keys ("images"), causing upload config (plugin_id, endpoint,
allowed_types) to be lost. This could prevent image uploads from
working correctly in the static-image plugin and others.
Changes:
- Add data-* attributes to the Jinja2 file-upload template so upload
config is embedded directly on the file input element
- Update getUploadConfig() in both file-upload.js and plugins_manager.js
to read config from data attributes first, falling back to schema lookup
- Remove duplicate handleFiles/handleFileDrop/handleFileSelect from
plugins_manager.js that overwrote the more robust file-upload.js versions
- Bump cache-busting version strings so browsers fetch updated JS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): harden file upload functions against CodeRabbit patterns
- Add response.ok guard before response.json() in handleFiles,
deleteUploadedFile, and handleCredentialsUpload to prevent
SyntaxError on non-JSON error responses (PR #271 finding)
- Remove duplicate getUploadConfig() from plugins_manager.js;
file-upload.js now owns this function exclusively
- Replace innerHTML with textContent/DOM methods in
handleCredentialsUpload to prevent XSS (PR #271 finding)
- Fix redundant if-check in getUploadConfig data-attribute reader
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): address CodeRabbit findings on file upload widget
- Add data-multiple="true" discriminator on array file inputs so
handleFileDrop routes multi-file drops to handleFiles() not
handleSingleFileUpload()
- Duplicate upload config data attributes onto drop zone wrapper so
getUploadConfig() survives progress-helper DOM re-renders that
remove the file input element
- Clear file input in finally block after credentials upload to allow
re-selecting the same file on retry
- Branch deleteUploadedFile on fileType: JSON deletes remove the DOM
element directly instead of routing through updateImageList() which
renders image-specific cards (thumbnails, scheduling controls)
Addresses CodeRabbit findings on PR #279:
- Major: drag-and-drop hits single-file path for array uploaders
- Major: config lookup fails after first upload (DOM node removed)
- Minor: same-file retry silently no-ops
- Major: JSON deletes re-render list as images
Co-Authored-By: 5ymb01 <5ymb01@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): address CodeRabbit round-2 findings on file upload widget
- Extract getConfigSourceElement() helper so handleFileDrop,
handleSingleFileUpload, and getUploadConfig all share the same
fallback logic: file input → drop zone wrapper
- Remove pluginId gate from getUploadConfig Strategy 1 — fields with
uploadEndpoint or fileType but no pluginId now return config instead
of falling through to generic defaults
- Fix JSON delete identifier mismatch: use file.id || file.category_name
(matching the renderer at line 3202) instead of f.file_id; remove
regex sanitization on DOM id lookup (renderer doesn't sanitize)
Addresses CodeRabbit round-2 findings on PR #279:
- Major: single-file uploads bypass drop-zone config fallback
- Major: getUploadConfig gated on data-plugin-id only
- Major: JSON delete file identifier mismatch vs renderer
Co-Authored-By: 5ymb01 <5ymb01@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): align delete handler file identifier with renderer logic
Remove f.file_id from JSON file delete filter to match the renderer's
identifier logic (file.id || file.category_name || idx). Prevents
deleted entries from persisting in the hidden input on next save.
Co-Authored-By: 5ymb01 <noreply@github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: 5ymb01 <5ymb01@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: 5ymb01 <noreply@github.com>
|
||
|
|
fe5c1d0d5e |
feat(web): add Google Calendar picker widget for dynamic multi-calendar selection (#274)
* fix(install): add --prefer-binary to pip installs to avoid /tmp exhaustion timezonefinder (~54 MB) includes large timezone polygon data files that pip unpacks into /tmp during installation. On Raspberry Pi, the default tmpfs /tmp size (often ~half of RAM) can be too small, causing the install to fail with an out-of-space error. Adding --prefer-binary tells pip to prefer pre-built binary wheels over source distributions. Since timezonefinder and most other packages publish wheels on PyPI (and piwheels.org has ARM wheels), this avoids the large temporary /tmp extraction and speeds up installs generally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(timezone): use America/New_York instead of EST for ESPN API date queries EST is a fixed UTC-5 offset that does not observe daylight saving time, causing the ESPN API date to be off by one hour during EDT (March–November). America/New_York correctly handles DST transitions. The ESPN scoreboard API anchors its schedule calendar to Eastern US time, so this Eastern timezone is intentionally kept for the API date — it is not user-configurable. Game time display is converted separately to the user's configured timezone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(web): add Google Calendar picker widget for dynamic calendar selection Adds a new google-calendar-picker widget and API endpoint that lets users load their available Google Calendars by name and check the ones they want, instead of manually typing calendar IDs. - GET /api/v3/plugins/calendar/list-calendars — calls plugin.get_calendars() and returns all accessible calendars with id, summary, and primary flag - google-calendar-picker.js — new widget: "Load My Calendars" button renders a checklist; selections update a hidden comma-separated input for form submit - plugin_config.html — handles x-widget: google-calendar-picker in array branch - base.html — loads the new widget script Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): address PR review findings in google-calendar-picker - api_v3.py: replace broad except block with specific exception handling, log full traceback via module logger, normalize/validate get_calendars() output to stable {id,summary,primary} objects, return opaque user-friendly error message instead of leaking str(e) - google-calendar-picker.js: fix button label only updating to "Refresh Calendars" on success (restore original label on error); update summary paragraph via syncHiddenAndSummary() on every checkbox change so UI stays in sync with hidden input; pass summary element through loadCalendars and renderCheckboxes instead of re-querying DOM - plugin_config.html: bound initWidget retry loop with MAX_RETRIES=40 to prevent infinite timers; normalize legacy comma-separated string values to arrays before passing to widget.render so pre-existing config populates correctly - install_dependencies_apt.py: update install_via_pip docstring to document both --break-system-packages and --prefer-binary flags Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): harden list_calendar_calendars input validation - Remove unused `as e` binding from ValueError/TypeError/KeyError except clause - Replace hasattr(__iter__) with isinstance(list|tuple) so non-sequence returns are rejected before iteration - Validate each calendar entry is a collections.abc.Mapping; skip and warn on malformed items rather than propagating a TypeError - Coerce id/summary to str safely if not already strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): skip calendar entries with empty id in list_calendar_calendars After coercing cal_id to str, check it is non-empty before appending to the calendars list so entries with no usable id are never forwarded to the client. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
eb143c44fa |
fix(web): render file-upload drop zone for string-type config fields (#271)
* feat: add March Madness plugin and tournament round logos New dedicated March Madness plugin with scrolling tournament ticker: - Fetches NCAA tournament data from ESPN scoreboard API - Shows seeded matchups with team logos, live scores, and round separators - Highlights upsets (higher seed beating lower seed) in gold - Auto-enables during tournament window (March 10 - April 10) - Configurable for NCAAM and NCAAW tournaments - Vegas mode support via get_vegas_content() Tournament round logo assets: - MARCH_MADNESS.png, ROUND_64.png, ROUND_32.png - SWEET_16.png, ELITE_8.png, FINAL_4.png, CHAMPIONSHIP.png Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(store): prevent bulk-update from stalling on bundled/in-repo plugins Three related bugs caused the bulk plugin update to stall at 3/19: 1. Bundled plugins (e.g. starlark-apps, shipped with LEDMatrix rather than the plugin registry) had no metadata file, so update_plugin() returned False → API returned 500 → frontend queue halted. Fix: check for .plugin_metadata.json with install_type=bundled and return True immediately (these plugins update with LEDMatrix itself). 2. git config --get remote.origin.url (without --local) walked up the directory tree and found the parent LEDMatrix repo's remote URL for plugins that live inside plugin-repos/. This caused the store manager to attempt a 60-second git clone of the wrong repo for every update. Fix: use --local to scope the lookup to the plugin directory only. 3. hello-world manifest.json had a trailing comma causing JSON parse errors on every plugin discovery cycle (fixed on devpi directly). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(march-madness): address PR #263 code review findings - Replace self.is_enabled with BasePlugin.self.enabled in update(), display(), and supports_dynamic_duration() so runtime toggles work - Support quarter-based period labels for NCAAW (Q1..Q4 vs H1..H2), detected via league key or status_detail content - Use live refresh interval (60s) for cache max_age during live games instead of hardcoded 300s - Narrow broad except in _load_round_logos to (OSError, ValueError) with a fallback except Exception using logger.exception for traces - Remove unused `situation` local variable from _parse_event() - Add numpy>=1.24.0 to requirements.txt (imported but was missing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): render file-upload drop zone for string-type config fields String fields with x-widget: "file-upload" were falling through to a plain text input because the template only handled the array case. Adds a dedicated drop zone branch for string fields and corresponding handleSingleFileSelect/handleSingleFileUpload JS handlers that POST to the x-upload-config endpoint. Fixes credentials.json upload for the calendar plugin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(march-madness): address PR #271 code review findings Inline fixes: - manager.py: swap min_duration/max_duration if misconfigured, log warning - manager.py: call session.close() and null session in cleanup() to prevent socket leaks on constrained hardware - manager.py: remove blocking network I/O from display(); update() is the sole fetch path (already uses 60s live-game interval) - manager.py: guard scroll_helper None before create_scrolling_image() in _create_ticker_image() to prevent crash when ScrollHelper is unavailable - store_manager.py: replace bare "except Exception: pass" with debug log including plugin_id and path when reading .plugin_metadata.json - file-upload.js: add endpoint guard (error if uploadEndpoint is falsy), client-side extension validation from data-allowed-extensions, and response.ok check before response.json() in handleSingleFileUpload - plugin_config.html: add data-allowed-extensions attribute to single-file input so JS handler can read the allowed extensions list Nitpick fixes: - manager.py: use logger.exception() (includes traceback) instead of logger.error() for league fetch errors - manager.py: remove redundant "{e}" from logger.exception() calls for round logo and March Madness logo load errors Not fixed (by design): - manifest.json repo naming: monorepo pattern is correct per project docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(march-madness): address second round of PR #271 code review findings Inline fixes: - requirements.txt: bump Pillow to >=9.1.0 (required for Image.Resampling.LANCZOS) - file-upload.js: replace all statusDiv.innerHTML assignments with safe DOM creation (textContent + createElement) to prevent XSS from untrusted strings - plugin_config.html: add role="button", tabindex="0", aria-label, onkeydown (Enter/Space) to drop zone for keyboard accessibility; add aria-live="polite" to status div for screen-reader announcements - file-upload.js: tighten handleFileDrop endpoint check to non-empty string (dataset.uploadEndpoint.trim() !== '') so an empty attribute falls back to the multi-file handler Nitpick fixes: - manager.py: remove redundant cached_image/cached_array reassignments after create_scrolling_image() which already sets them internally - manager.py: narrow bare except in _get_team_logo to (FileNotFoundError, OSError, ValueError) for expected I/O errors; log unexpected exceptions - store_manager.py: narrow except to (OSError, ValueError) when reading .plugin_metadata.json so unrelated exceptions propagate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
302235a357 |
feat: Starlark Apps Integration with Schema-Driven Config + Security Hardening (#253)
* feat: integrate Starlark/Tronbyte app support into plugin system Add starlark-apps plugin that renders Tidbyt/Tronbyte .star apps via Pixlet binary and integrates them into the existing Plugin Manager UI as virtual plugins. Includes vegas scroll support, Tronbyte repository browsing, and per-app configuration. - Extract working starlark plugin code from starlark branch onto fresh main - Fix plugin conventions (get_logger, VegasDisplayMode, BasePlugin) - Add 13 starlark API endpoints to api_v3.py (CRUD, browse, install, render) - Virtual plugin entries (starlark:<app_id>) in installed plugins list - Starlark-aware toggle and config routing in pages_v3.py - Tronbyte repository browser section in Plugin Store UI - Pixlet binary download script (scripts/download_pixlet.sh) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(starlark): use bare imports instead of relative imports Plugin loader uses spec_from_file_location without package context, so relative imports (.pixlet_renderer) fail. Use bare imports like all other plugins do. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(starlark): make API endpoints work standalone in web service The web service runs as a separate process with display_manager=None, so plugins aren't instantiated. Refactor starlark API endpoints to read/write the manifest file directly when the plugin isn't loaded, enabling full CRUD operations from the web UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(starlark): make config partial work standalone in web service Read starlark app data from manifest file directly when the plugin isn't loaded, matching the api_v3.py standalone pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(starlark): always show editable timing settings in config panel Render interval and display duration are now always editable in the starlark app config panel, not just shown as read-only status text. App-specific settings from schema still appear below when present. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(store): add sort, filter, search, and pagination to Plugin Store and Starlark Apps Plugin Store: - Live search with 300ms debounce (replaces Search button) - Sort dropdown: A→Z, Z→A, Category, Author, Newest - Installed toggle filter (All / Installed / Not Installed) - Per-page selector (12/24/48) with pagination controls - "Installed" badge and "Reinstall" button on already-installed plugins - Active filter count badge + clear filters button Starlark Apps: - Parallel bulk manifest fetching via ThreadPoolExecutor (20 workers) - Server-side 2-hour cache for all 500+ Tronbyte app manifests - Auto-loads all apps when section expands (no Browse button) - Live search, sort (A→Z, Z→A, Category, Author), author dropdown - Installed toggle filter, per-page selector (24/48/96), pagination - "Installed" badge on cards, "Reinstall" button variant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(store): move storeFilterState to global scope to fix scoping bug storeFilterState, pluginStoreCache, and related variables were declared inside an IIFE but referenced by top-level functions, causing ReferenceError that broke all plugin loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(starlark): schema-driven config forms + critical security fixes ## Schema-Driven Config UI - Render type-appropriate form inputs from schema.json (text, dropdown, toggle, color, datetime, location) - Pre-populate config.json with schema defaults on install - Auto-merge schema defaults when loading existing apps (handles schema updates) - Location fields: 3-part mini-form (lat/lng/timezone) assembles into JSON - Toggle fields: support both boolean and string "true"/"false" values - Unsupported field types (oauth2, photo_select) show warning banners - Fallback to raw key/value inputs for apps without schema ## Critical Security Fixes (P0) - **Path Traversal**: Verify path safety BEFORE mkdir to prevent TOCTOU - **Race Conditions**: Add file locking (fcntl) + atomic writes to manifest operations - **Command Injection**: Validate config keys/values with regex before passing to Pixlet subprocess ## Major Logic Fixes (P1) - **Config/Manifest Separation**: Store timing keys (render_interval, display_duration) ONLY in manifest - **Location Validation**: Validate lat [-90,90] and lng [-180,180] ranges, reject malformed JSON - **Schema Defaults Merge**: Auto-apply new schema defaults to existing app configs on load - **Config Key Validation**: Enforce alphanumeric+underscore format, prevent prototype pollution ## Files Changed - web_interface/templates/v3/partials/starlark_config.html — schema-driven form rendering - plugin-repos/starlark-apps/manager.py — file locking, path safety, config validation, schema merge - plugin-repos/starlark-apps/pixlet_renderer.py — config value sanitization - web_interface/blueprints/api_v3.py — timing key separation, safe manifest updates Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): use manifest filename field for .star downloads Tronbyte apps don't always name their .star file to match the directory. For example, the "analogclock" app has "analog_clock.star" (with underscore). The manifest.yaml contains a "filename" field with the correct name. Changes: - download_star_file() now accepts optional filename parameter - Install endpoint passes metadata['filename'] to download_star_file() - Falls back to {app_id}.star if filename not in manifest Fixes: "Failed to download .star file for analogclock" error Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): reload tronbyte_repository module to pick up code changes The web service caches imported modules in sys.modules. When deploying code updates, the old cached version was still being used. Now uses importlib.reload() when module is already loaded. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): use correct 'fileName' field from manifest (camelCase) The Tronbyte manifest uses 'fileName' (camelCase), not 'filename' (lowercase). This caused the download to fall back to {app_id}.star which doesn't exist for apps like analogclock (which has analog_clock.star). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat(starlark): extract schema during standalone install The standalone install function (_install_star_file) wasn't extracting schema from .star files, so apps installed via the web service had no schema.json and the config panel couldn't render schema-driven forms. Now uses PixletRenderer to extract schema during standalone install, same as the plugin does. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat(starlark): implement source code parser for schema extraction Pixlet CLI doesn't support schema extraction (--print-schema flag doesn't exist), so apps were being installed without schemas even when they have them. Implemented regex-based .star file parser that: - Extracts get_schema() function from source code - Parses schema.Schema(version, fields) structure - Handles variable-referenced dropdown options (e.g., options = dialectOptions) - Supports Location, Text, Toggle, Dropdown, Color, DateTime fields - Gracefully handles unsupported fields (OAuth2, LocationBased, etc.) - Returns formatted JSON matching web UI template expectations Coverage: 90%+ of Tronbyte apps (static schemas + variable references) Changes: - Replace extract_schema() to parse .star files directly instead of using Pixlet CLI - Add 6 helper methods for parsing schema structure - Handle nested parentheses and brackets properly - Resolve variable references for dropdown options Tested with: - analog_clock.star (Location field) ✓ - Multi-field test (Text + Dropdown + Toggle) ✓ - Variable-referenced options ✓ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): add List to typing imports for schema parser Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): load schema from schema.json in standalone mode The standalone API endpoint was returning schema: null because it didn't load the schema.json file. Now reads schema from disk when returning app details via web service. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat(starlark): implement schema extraction, asset download, and config persistence ## Schema Extraction - Replace broken `pixlet serve --print-schema` with regex-based source parser - Extract schema by parsing `get_schema()` function from .star files - Support all field types: Location, Text, Toggle, Dropdown, Color, DateTime - Handle variable-referenced dropdown options (e.g., `options = teamOptions`) - Gracefully handle complex/unsupported field types (OAuth2, PhotoSelect, etc.) - Extract schema for 90%+ of Tronbyte apps ## Asset Download - Add `download_app_assets()` to fetch images/, sources/, fonts/ directories - Download assets in binary mode for proper image/font handling - Validate all paths to prevent directory traversal attacks - Copy asset directories during app installation - Enable apps like AnalogClock that require image assets ## Config Persistence - Create config.json file during installation with schema defaults - Update both config.json and manifest when saving configuration - Load config from config.json (not manifest) for consistency with plugin - Separate timing keys (render_interval, display_duration) from app config - Fix standalone web service mode to read/write config.json ## Pixlet Command Fix - Fix Pixlet CLI invocation: config params are positional, not flags - Change from `pixlet render file.star -c key=value` to `pixlet render file.star key=value -o output` - Properly handle JSON config values (e.g., location objects) - Enable config to be applied during rendering ## Security & Reliability - Add threading.Lock for cache operations to prevent race conditions - Reduce ThreadPoolExecutor workers from 20 to 5 for Raspberry Pi - Add path traversal validation in download_star_file() - Add YAML error logging in manifest fetching - Add file size validation (5MB limit) for .star uploads - Use sanitized app_id consistently in install endpoints - Use atomic manifest updates to prevent race conditions - Add missing Optional import for type hints ## Web UI - Fix standalone mode schema loading in config partial - Schema-driven config forms now render correctly for all apps - Location fields show lat/lng/timezone inputs - Dropdown, toggle, text, color, and datetime fields all supported Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): code review fixes - security, robustness, and schema parsing ## Security Fixes - manager.py: Check _update_manifest_safe return values to prevent silent failures - manager.py: Improve temp file cleanup in _save_manifest to prevent leaks - manager.py: Fix uninstall order (manifest → memory → disk) for consistency - api_v3.py: Add path traversal validation in uninstall endpoint - api_v3.py: Implement atomic writes for manifest files with temp + rename - pixlet_renderer.py: Relax config validation to only block dangerous shell metacharacters ## Frontend Robustness - plugins_manager.js: Add safeLocalStorage wrapper for restricted contexts (private browsing) - starlark_config.html: Scope querySelector to container to prevent modal conflicts ## Schema Parsing Improvements - pixlet_renderer.py: Indentation-aware get_schema() extraction (handles nested functions) - pixlet_renderer.py: Handle quoted defaults with commas (e.g., "New York, NY") - tronbyte_repository.py: Validate file_name is string before path traversal checks ## Dependencies - requirements.txt: Update Pillow (10.4.0), PyYAML (6.0.2), requests (2.32.0) ## Documentation - docs/STARLARK_APPS_GUIDE.md: Comprehensive guide explaining: - How Starlark apps work - That apps come from Tronbyte (not LEDMatrix) - Installation, configuration, troubleshooting - Links to upstream projects All changes improve security, reliability, and user experience. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): convert Path to str in spec_from_file_location calls The module import helpers were passing Path objects directly to spec_from_file_location(), which caused spec to be None. This broke the Starlark app store browser. - Convert module_path to string in both _get_tronbyte_repository_class and _get_pixlet_renderer_class - Add None checks with clear error messages for debugging Fixes: spec not found for the module 'tronbyte_repository' Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(starlark): restore Starlark Apps section in plugins.html The Starlark Apps UI section was lost during merge conflict resolution with main branch. Restored from commit |
||
|
|
636d0e181c |
feat(plugins): add sorting, filtering, and fix Update All button (#252)
* feat(store): add sorting, filtering, and fix Update All button Add client-side sorting and filtering to the Plugin Store: - Sort by A-Z, Z-A, Verified First, Recently Updated, Category - Filter by verified, new, installed status, author, and tags - Installed/Update Available badges on store cards - Active filter count badge with clear-all button - Sort preference persisted to localStorage Fix three bugs causing button unresponsiveness: - pluginsInitialized never reset on HTMX tab navigation (root cause of Update All silently doing nothing on second visit) - htmx:afterSwap condition too broad (fired on unrelated swaps) - data-running guard tied to DOM element replaced by cloneNode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(store): replace tag pills with category pills, fix sort dates - Replace tag filter pills with category filter pills (less duplication) - Prefer per-plugin last_updated over repo-wide pushed_at for sort Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * debug: add console logging to filter/sort handlers * fix: bump cache-buster versions for JS and CSS * feat(plugins): add sorting to installed plugins section Add A-Z, Z-A, and Enabled First sort options for installed plugins with localStorage persistence. Both installed and store sections now default to A-Z sorting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(store): consolidate CSS, fix stale cache bug, add missing utilities, fix icon - Consolidate .filter-pill and .category-filter-pill into shared selectors and scope transition to only changed properties - Fix applyStoreFiltersAndSort ignoring fresh server-filtered results by accepting optional basePlugins parameter - Add missing .py-1.5 and .rounded-full CSS utility classes - Replace invalid fa-sparkles with fa-star (FA 6.0.0 compatible) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(store): semver-aware update badge and add missing gap-1.5 utility - Replace naive version !== comparison with isNewerVersion() that does semver greater-than check, preventing false "Update" badges on same-version or downgrade scenarios - Add missing .gap-1.5 CSS utility used by category pills and tag lists Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
bc8568604a |
feat(web): add LED RGB sequence, multiplexing, and panel type settings (#248)
* feat(web): add LED RGB sequence, multiplexing, and panel type settings Expose three rpi-rgb-led-matrix hardware options in the Display Settings UI so users can configure non-standard panels without editing config.json manually. All defaults match existing behavior (RGB, Direct, Standard). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api): validate led_rgb_sequence, multiplexing, and panel_type inputs Reject invalid values with 400 errors before writing to config: whitelist check for led_rgb_sequence and panel_type, range + type check for multiplexing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
82370a0253 |
Fix log viewer readability — add missing CSS utility classes (#244)
* fix(web): add missing utility classes for log viewer readability The log viewer uses text-gray-100, text-gray-200, text-gray-300, text-red-300, text-yellow-300, bg-gray-800, bg-red-900, bg-yellow-900, border-gray-700, and hover:bg-gray-800 — none of which were defined in app.css. Without definitions, log text inherited the body's dark color (#111827) which was invisible against the dark bg-gray-900 log container in light mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): remove dead bg-opacity classes, use proper log level colors The bg-opacity-10/bg-opacity-30 classes set a --bg-opacity CSS variable that no background-color rule consumed, making them dead code. Replace the broken two-class pattern (e.g. "bg-red-900 bg-opacity-10") with dedicated log-level-error/warning/debug classes that use rgb() with actual alpha values. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
3975940cff |
Add light/dark mode toggle and fix log readability (#243)
* feat(web): add light/dark mode toggle and fix log readability Add a theme toggle button (moon/sun icon) to the header that switches between light and dark mode. Theme preference persists in localStorage and falls back to the OS prefers-color-scheme setting. The implementation uses a data-theme attribute on <html> with CSS overrides, so all 13 partial templates and 20+ widget JS files get dark mode support without any modifications — only 3 files changed. Also fixes log timestamp readability: text-gray-500 had ~3.5:1 contrast ratio against the dark log background, now uses text-gray-400 (~5.3:1) which passes WCAG AA in both light and dark mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): address dark mode review — accessibility, robustness, and code quality - WCAG touch target: enforce 44×44px minimum on theme toggle button with display:inline-flex centering - Accessibility: add type="button", aria-pressed (dynamically updated), aria-hidden on decorative icons, and contextual aria-label/title that reflects current state ("Switch to light/dark mode") - Robustness: wrap all localStorage and matchMedia calls in try/catch with fallbacks for private browsing and restricted contexts; use addListener fallback for older browsers lacking addEventListener - Stylelint: convert all rgba() to modern rgb(…/…%) notation across both light and dark theme shadows and gradients - DRY: replace hardcoded hex values in dark mode utility overrides and component overrides with CSS variable references (--color-surface, --color-background, --color-border, --color-text-primary, etc.) - Remove redundant [data-theme="dark"] body rule (body already uses CSS variables that are redefined under the dark theme selector) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
9a72adbde1 |
fix(web): unify operation history tracking for monorepo plugin operations (#240)
The operation history UI was reading from the wrong data source (operation_queue instead of operation_history), install/update records lacked version details, toggle operations used a type name that didn't match UI filters, and the Clear History button was non-functional. - Switch GET /plugins/operation/history to read from OperationHistory audit log with return type hint and targeted exception handling - Add DELETE /plugins/operation/history endpoint; wire up Clear button - Add _get_plugin_version helper with specific exception handling (FileNotFoundError, PermissionError, json.JSONDecodeError) and structured logging with plugin_id/path context - Record plugin version, branch, and commit details on install/update - Record install failures in the direct (non-queue) code path - Replace "toggle" operation type with "enable"/"disable" - Add normalizeStatus() in JS to map completed→success, error→failed so status filter works regardless of server-side convention - Truncate commit SHAs to 7 chars in details display - Fix HTML filter options, operation type colors, duplicate JS init Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
448a15c1e6 |
feat(fonts): add dynamic font selection and font manager improvements (#232)
* feat(fonts): add dynamic font selection and font manager improvements - Add font-selector widget for dynamic font selection in plugin configs - Enhance /api/v3/fonts/catalog with filename, display_name, and type - Add /api/v3/fonts/preview endpoint for server-side font rendering - Add /api/v3/fonts/<family> DELETE endpoint with system font protection - Fix /api/v3/fonts/upload to actually save uploaded font files - Update font manager tab with dynamic dropdowns, server-side preview, and font deletion - Add new BDF fonts: 6x10, 6x12, 6x13, 7x13, 7x14, 8x13, 9x15, 9x18, 10x20 (with bold/oblique variants) - Add tom-thumb, helvR12, clR6x12, texgyre-27 fonts Plugin authors can use x-widget: "font-selector" in schemas to enable dynamic font selection that automatically shows all available fonts. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): security fixes and code quality improvements - Fix README.md typos and add language tags to code fences - Remove duplicate delete_font function causing Flask endpoint collision - Add safe integer parsing for size parameter in preview endpoint - Fix path traversal vulnerability in /fonts/preview endpoint - Fix path traversal vulnerability in /fonts/<family> DELETE endpoint - Fix XSS vulnerability in fonts.html by using DOM APIs instead of innerHTML - Move baseUrl to shared scope to fix ReferenceError in multiple functions Security improvements: - Validate font filenames reject path separators and '..' - Validate paths are within fonts_dir before file operations - Use textContent and data attributes instead of inline onclick handlers - Restrict file extensions to known font types Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): address code issues and XSS vulnerabilities - Move `import re` to module level, remove inline imports - Remove duplicate font_file assignment in upload_font() - Remove redundant validation with inconsistent allowed extensions - Remove redundant PathLib import, use already-imported Path - Fix XSS vulnerabilities in fonts.html by using DOM APIs instead of innerHTML with template literals for user-controlled data Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): add size limits to font preview endpoint Add input validation to prevent DoS via large image generation: - MAX_TEXT_CHARS (100): Limit text input length - MAX_TEXT_LINES (3): Limit number of newlines - MAX_DIM (1024): Limit max width/height - MAX_PIXELS (500000): Limit total pixel count Validates text early before processing and checks computed dimensions after bbox calculation but before image allocation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): improve error handling, catalog keys, and BDF preview - Add structured logging for cache invalidation failures instead of silent pass (FontUpload, FontDelete, FontCatalog contexts) - Use filename as unique catalog key to prevent collisions when multiple font files share the same family_name from metadata - Return explicit error for BDF font preview instead of showing misleading preview with default font Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): address nitpick issues in font management Frontend (fonts.html): - Remove unused escapeHtml function (dead code) - Add max-attempts guard (50 retries) to initialization loop - Add response.ok checks before JSON parsing in deleteFont, addFontOverride, deleteFontOverride, uploadSelectedFonts - Use is_system flag from API instead of hardcoded client-side list Backend (api_v3.py): - Move SYSTEM_FONTS to module-level frozenset for single source of truth - Add is_system flag to font catalog entries - Simplify delete_font system font check using frozenset lookup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): align frontend upload validation with backend - Add .otf to accepted file extensions (HTML accept attribute, JS filter) - Update validation regex to allow hyphens (matching backend) - Preserve hyphens in auto-generated font family names - Update UI text to reflect all supported formats Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): fix lint errors and missing variable - Remove unused exception binding in set_cached except block - Define font_family_lower before case-insensitive fallback loop - Add response.ok check to font preview fetch (consistent with other handlers) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): address nitpick code quality issues - Add return type hints to get_font_preview and delete_font endpoints - Catch specific PIL exceptions (IOError/OSError) when loading fonts - Replace innerHTML with DOM APIs for trash icon (consistency) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(fonts): remove unused exception bindings in cache-clearing blocks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
4a9fc2df3a |
feat(web): add shutdown button to Quick Actions (#234)
Add a "Shutdown System" button to the Overview page that gracefully powers off the Raspberry Pi. Uses sudo poweroff, consistent with the existing reboot_system action, letting sudo's secure_path handle binary resolution. Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
8912501604 |
fix(web): ensure unchecked checkboxes save as false in main config forms (#222)
* fix: remove plugin-specific calendar duration from config template
Plugin display durations should be added dynamically when plugins are
installed, not hardcoded in the template.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(web): ensure unchecked checkboxes save as false in main config forms
HTML checkboxes omit their key entirely when unchecked, so the backend
never received updates to set boolean values to false. This affected:
- vegas_scroll_enabled: Now uses _coerce_to_bool helper
- use_short_date_format: Now uses _coerce_to_bool helper
- Plugin system checkboxes (auto_discover, auto_load_enabled, development_mode):
Now uses _coerce_to_bool helper
- Hardware checkboxes (disable_hardware_pulsing, inverse_colors, show_refresh_rate):
Now uses _coerce_to_bool helper
- web_display_autostart: Now uses _coerce_to_bool helper
Added _coerce_to_bool() helper function that properly converts form string
values ("true", "on", "1", "yes") to actual Python booleans, ensuring
consistent JSON types in config and correct downstream boolean checks.
Also added value="true" to all main config checkboxes for consistent boolean
parsing (sends "true" instead of "on" when checked).
This is the same issue fixed in commit
|
||
|
|
14c50f316e |
feat: add timezone support for schedules and dim schedule feature (#218)
* feat: add timezone support for schedules and dim schedule feature - Fix timezone handling in _check_schedule() to use configured timezone instead of system time (addresses schedule offset issues) - Add dim schedule feature for automatic brightness dimming: - New dim_schedule config section with brightness level and time windows - Smart interaction: dim schedule won't turn display on if it's off - Supports both global and per-day modes like on/off schedule - Add set_brightness() and get_brightness() methods to DisplayManager for runtime brightness control - Add REST API endpoints: GET/POST /api/v3/config/dim-schedule - Add web UI for dim schedule configuration in schedule settings page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: normalize per-day mode and validate dim_brightness input - Normalize mode string in _check_dim_schedule to handle both "per-day" and "per_day" variants - Add try/except around dim_brightness int conversion to handle invalid input gracefully Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: improve error handling in brightness and dim schedule endpoints - display_manager.py: Add fail-fast input validation, catch specific exceptions (AttributeError, TypeError, ValueError), add [BRIGHTNESS] context tags, include stack traces in error logs - api_v3.py: Catch specific config exceptions (FileNotFoundError, JSONDecodeError, IOError), add [DIM SCHEDULE] context tags for Pi debugging, include stack traces Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
7524747e44 |
Feature/vegas scroll mode (#215)
* feat(display): add Vegas-style continuous scroll mode Implement an opt-in Vegas ticker mode that composes all enabled plugin content into a single continuous horizontal scroll. Includes a modular package (src/vegas_mode/) with double-buffered streaming, 125 FPS render pipeline using the existing ScrollHelper, live priority interruption support, and a web UI for configuration with drag-drop plugin ordering. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(vegas): add three-mode display system (SCROLL, FIXED_SEGMENT, STATIC) Adds a flexible display mode system for Vegas scroll mode that allows plugins to control how their content appears in the continuous scroll: - SCROLL: Content scrolls continuously (multi-item plugins like sports) - FIXED_SEGMENT: Fixed block that scrolls by (clock, weather) - STATIC: Scroll pauses, plugin displays, then resumes (alerts) Changes: - Add VegasDisplayMode enum to base_plugin.py with backward-compatible mapping from legacy get_vegas_content_type() - Add static pause handling to coordinator with scroll position save/restore - Add mode-aware content composition to stream_manager - Add vegas_mode info to /api/v3/plugins/installed endpoint - Add mode indicators to Vegas settings UI - Add comprehensive plugin developer documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas,widgets): address validation, thread safety, and XSS issues Vegas mode fixes: - config.py: align validation limits with UI (scroll_speed max 200, separator_width max 128) - coordinator.py: fix race condition by properly initializing _pending_config - plugin_adapter.py: remove unused import - render_pipeline.py: preserve deque type in reset() method - stream_manager.py: fix lock handling and swap_buffers to truly swap API fixes: - api_v3.py: normalize boolean checkbox values, validate numeric fields, ensure JSON arrays Widget fixes: - day-selector.js: remove escapeHtml from JSON.stringify to prevent corruption - password-input.js: use deterministic color class mapping for Tailwind JIT - radio-group.js: replace inline onchange with addEventListener to prevent XSS - select-dropdown.js: guard global registry access - slider.js: add escapeAttr for attributes, fix null dereference in setValue Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): improve exception handling and static pause state management coordinator.py: - _check_live_priority: use logger.exception for full traceback - _end_static_pause: guard scroll resume on interruption (stop/live priority) - _update_static_mode_plugins: log errors instead of silently swallowing render_pipeline.py: - compose_scroll_content: use specific exceptions and logger.exception - render_frame: use specific exceptions and logger.exception - hot_swap_content: use specific exceptions and logger.exception Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): add interrupt mechanism and improve config/exception handling - Add interrupt checker callback to Vegas coordinator for responsive handling of on-demand requests and wifi status during Vegas mode - Fix config.py update() to include dynamic duration fields - Fix is_plugin_included() consistency with get_ordered_plugins() - Update _apply_pending_config to propagate config to StreamManager - Change _fetch_plugin_content to use logger.exception for traceback - Replace bare except in _refresh_plugin_list with specific exceptions - Add aria-label accessibility to Vegas toggle checkbox - Fix XSS vulnerability in plugin metadata rendering with escapeHtml Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): improve logging, validation, lock handling, and config updates - display_controller.py: use logger.exception for Vegas errors with traceback - base_plugin.py: validate vegas_panel_count as positive integer with warning - coordinator.py: fix _apply_pending_config to avoid losing concurrent updates by clearing _pending_config while holding lock - plugin_adapter.py: remove broad catch-all, use narrower exception types (AttributeError, TypeError, ValueError, OSError, RuntimeError) and logger.exception for traceback preservation - api_v3.py: only update vegas_config['enabled'] when key is present in data to prevent incorrect disabling when checkbox is omitted Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): improve cycle advancement, logging, and accessibility - Add advance_cycle() method to StreamManager for clearing buffer between cycles - Call advance_cycle() in RenderPipeline.start_new_cycle() for fresh content - Use logger.exception() for interrupt check and static pause errors (full tracebacks) - Add id="vegas_scroll_label" to h3 for aria-labelledby reference - Call updatePluginConfig() after rendering plugin list for proper initialization Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): add thread-safety, preserve updates, and improve logging - display_controller.py: Use logger.exception() for Vegas import errors - plugin_adapter.py: Add thread-safe cache lock, remove unused exception binding - stream_manager.py: In-place merge in process_updates() preserves non-updated plugins - api_v3.py: Change vegas_scroll_enabled default from False to True Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): add debug logging and narrow exception types - stream_manager.py: Log when get_vegas_display_mode() is unavailable - stream_manager.py: Narrow exception type from Exception to (AttributeError, TypeError) - api_v3.py: Log exceptions when reading Vegas display metadata with plugin context Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): fix method call and improve exception logging - Fix _check_vegas_interrupt() calling nonexistent _check_wifi_status(), now correctly calls _check_wifi_status_message() - Update _refresh_plugin_list() exception handler to use logger.exception() with plugin_id and class name for remote debugging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(web): replace complex toggle with standard checkbox for Vegas mode The Tailwind pseudo-element toggle (after:content-[''], etc.) wasn't rendering because these classes weren't in the CSS bundle. Replaced with a simple checkbox that matches other form controls in the template. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * debug(vegas): add detailed logging to _refresh_plugin_list Track why plugins aren't being found for Vegas scroll: - Log count of loaded plugins - Log enabled status for each plugin - Log content_type and display_mode checks - Log when plugin_manager lacks loaded_plugins Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): use correct attribute name for plugin manager StreamManager and VegasModeCoordinator were checking for plugin_manager.loaded_plugins but PluginManager stores active plugins in plugin_manager.plugins. This caused Vegas scroll to find zero plugins despite plugins being available. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): convert scroll_speed from px/sec to px/frame correctly The config scroll_speed is in pixels per second, but ScrollHelper in frame_based_scrolling mode interprets it as pixels per frame. Previously this caused the speed to be clamped to max 5.0 regardless of the configured value. Now properly converts: pixels_per_frame = scroll_speed * scroll_delay With defaults (50 px/s, 0.02s delay), this gives 1 px/frame = 50 px/s. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(vegas): add FPS logging every 5 seconds Logs actual FPS vs target FPS to help diagnose performance issues. Shows frame count in each 5-second interval. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): improve plugin content capture reliability - Call update_data() before capture to ensure fresh plugin data - Try display() without force_clear first, fallback if TypeError - Retry capture with force_clear=True if first attempt is blank - Use histogram-based blank detection instead of point sampling (more reliable for content positioned anywhere in frame) This should help capture content from plugins that don't implement get_vegas_content() natively. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): handle callable width/height on display_manager DisplayManager.width and .height may be methods or properties depending on the implementation. Use callable() check to call them if needed, ensuring display_width and display_height are always integers. Fixes potential TypeError when width/height are methods. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): use logger.exception for display mode errors Replace logger.error with logger.exception to capture full stack trace when get_vegas_display_mode() fails on a plugin. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): protect plugin list updates with buffer lock Move assignment of _ordered_plugins and index resets under _buffer_lock to prevent race conditions with _prefetch_content() which reads these variables under the same lock. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): catch all exceptions in get_vegas_display_mode Broaden exception handling from AttributeError/TypeError to Exception so any plugin error in get_vegas_display_mode() doesn't abort the entire plugin list refresh. The loop continues with the default FIXED_SEGMENT mode. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(vegas): refresh stream manager when config updates After updating stream_manager.config, force a refresh to pick up changes to plugin_order, excluded_plugins, and buffer_ahead settings. Also use logger.exception to capture full stack traces on config update errors. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * debug(vegas): add detailed logging for blank image detection * feat(vegas): extract full scroll content from plugins using ScrollHelper Plugins like ledmatrix-stocks and odds-ticker use ScrollHelper with a cached_image that contains their full scrolling content. Instead of falling back to single-frame capture, now check for scroll_helper.cached_image first to get the complete scrolling content for Vegas mode. * debug(vegas): add comprehensive INFO-level logging for plugin content flow - Log each plugin being processed with class name - Log which content methods are tried (native, scroll_helper, fallback) - Log success/failure of each method with image dimensions - Log brightness check results for blank image detection - Add visual separators in logs for easier debugging - Log plugin list refresh with enabled/excluded status Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(vegas): trigger scroll content generation when cache is empty When a plugin has a scroll_helper but its cached_image is not yet populated, try to trigger content generation by: 1. Calling _create_scrolling_display() if available (stocks pattern) 2. Calling display(force_clear=True) as a fallback This allows plugins like stocks to provide their full scroll content even when Vegas mode starts before the plugin has run its normal display cycle. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve exception handling in plugin_adapter scroll content retrieval Replace broad except Exception handlers with narrow exception types (AttributeError, TypeError, ValueError, OSError) and use logger.exception instead of logger.warning/info to capture full stack traces for better diagnosability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: narrow exception handling in coordinator and plugin_adapter - coordinator.py: Replace broad Exception catch around get_vegas_display_mode() with (AttributeError, TypeError) and use logger.exception for stack traces - plugin_adapter.py: Narrow update_data() exception handler to (AttributeError, RuntimeError, OSError) and use logger.exception Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve Vegas mode robustness and API validation - display_controller: Guard against None plugin_manager in Vegas init - coordinator: Restore scrolling state in resume() to match pause() - api_v3: Validate Vegas numeric fields with range checks and 400 errors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
10d70d911a |
Fix unchecked boolean checkboxes not saving as false (#216)
* fix(web): ensure unchecked boolean checkboxes save as false HTML checkboxes don't submit values when unchecked. The plugin config save endpoint starts from existing config (for partial updates), so an unchecked checkbox's old `true` value persists. Additionally, merge_with_defaults fills in schema defaults for missing fields, causing booleans with `"default": true` to always re-enable. This affected the odds-ticker plugin where NFL/NBA leagues (default: true) could not be disabled via the checkbox UI, while NHL (default: false) appeared to work by coincidence. Changes: - Add _set_missing_booleans_to_false() that walks the schema after form processing and sets any boolean field absent from form data to false - Add value="true" to boolean checkboxes so checked state sends "true" instead of "on" (proper boolean parsing) - Handle "on"/"off" strings in _parse_form_value_with_schema for backwards compatibility with checkboxes lacking value="true" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(web): guard on/off coercion to boolean schema types, handle arrays - Only coerce "on"/"off" strings to booleans when the schema type is boolean; "true"/"false" remain unconditional - Extend _set_missing_booleans_to_false to recurse into arrays of objects (e.g. custom_feeds.0.enabled) by discovering item indices from submitted form keys and recursing per-index Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(web): preserve array structures when setting missing booleans _set_nested_value uses dict-style access for all path segments, which corrupts lists when paths contain numeric array indices (e.g. "feeds.custom_feeds.0.enabled"). Refactored _set_missing_booleans_to_false to: - Accept an optional config_node parameter for direct array item access - When inside an array item, set booleans directly on the item dict - Navigate to array lists manually, preserving their list type - Ensure array items exist as dicts before recursing This prevents array-of-object configs (like custom_feeds) from being converted to nested dicts with numeric string keys. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
a8c85dd015 |
feat(widgets): add modular widget system for schedule and common inputs (#213)
* feat(widgets): add modular widget system for schedule and common inputs Add 15 new reusable widgets following the widget registry pattern: - schedule-picker: composite widget for enable/mode/time configuration - day-selector: checkbox group for days of the week - time-range: paired start/end time inputs with validation - text-input, number-input, textarea: enhanced text inputs - toggle-switch, radio-group, select-dropdown: selection widgets - slider, color-picker, date-picker: specialized inputs - email-input, url-input, password-input: validated string inputs Refactor schedule.html to use the new schedule-picker widget instead of inline JavaScript. Add x-widget support in plugin_config.html for all new widgets so plugins can use them via schema configuration. Fix form submission for checkboxes by using hidden input pattern to ensure unchecked state is properly sent via JSON-encoded forms. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): improve security, validation, and form binding across widgets - Fix XSS vulnerability: escapeHtml now escapes quotes in all widget fallbacks - color-picker: validate presets with isValidHex(), use data attributes - date-picker: add placeholder attribute support - day-selector: use options.name for hidden input form binding - password-input: implement requireUppercase/Number/Special validation - radio-group: fix value injection using this.value instead of interpolation - schedule-picker: preserve day values when disabling (don't clear times) - select-dropdown: remove undocumented searchable/icons options - text-input: apply patternMessage via setCustomValidity - time-range: use options.name for hidden inputs - toggle-switch: preserve configured color from data attribute - url-input: combine browser and custom protocol validation - plugin_config: add widget support for boolean/number types, pass name to day-selector - schedule: handle null config gracefully, preserve explicit mode setting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): validate day-selector input, consistent minLength default, escape JSON quotes - day-selector: filter incoming selectedDays to only valid entries in DAYS array (prevents invalid persisted values from corrupting UI/state) - password-input: use default minLength of 8 when not explicitly set (fixes inconsistency between render() and onInput() strength meter baseline) - plugin_config.html: escape single quotes in JSON hidden input values (prevents broken attributes when JSON contains single quotes) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(widgets): add global notification widget, consolidate duplicated code - Create notification.js widget with toast-style notifications - Support for success, error, warning, info types - Auto-dismiss with configurable duration - Stacking support with max notifications limit - Accessible with aria-live and role="alert" - Update base.html to load notification widget early - Replace duplicate showNotification in raw_json.html - Simplify fonts.html fallback notification - Net reduction of ~66 lines of duplicated code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): escape options.name in all widgets, validate day-selector format Security fixes: - Escape options.name attribute in all 13 widgets to prevent injection - Affected: color-picker, date-picker, email-input, number-input, password-input, radio-group, select-dropdown, slider, text-input, textarea, toggle-switch, url-input Defensive coding: - day-selector: validate format option exists in DAY_LABELS before use - Falls back to 'long' format for unsupported/invalid format values Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(plugins): add type="button" to control buttons, add debug logging - Add type="button" attribute to refresh, update-all, and restart buttons to prevent potential form submission behavior - Add console logging to diagnose button click issues: - Log when event listeners are attached (and whether buttons found) - Log when handler functions are called Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): improve security and validation across widget inputs - color-picker.js: Add sanitizeHex() to validate hex values before HTML interpolation, ensuring only safe #rrggbb strings are used - day-selector.js: Escape inputName in hidden input name attribute - number-input.js: Sanitize and escape currentValue in input element - password-input.js: Validate minLength as non-negative integer, clamp invalid values to default of 8 - slider.js: Add null check for input element before accessing value - text-input.js: Clear custom validity before checkValidity() to avoid stale errors, re-check after setting pattern message - url-input.js: Normalize allowedProtocols to array, filter to valid protocol strings, and escape before HTML interpolation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): add defensive fallback for DAY_LABELS lookup in day-selector Extract labelMap with fallback before loop to ensure safe access even if format validation somehow fails. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(widgets): add timezone-selector widget with IANA timezone dropdown - Create timezone-selector.js widget with comprehensive IANA timezone list - Group timezones by region (US & Canada, Europe, Asia, etc.) - Show current UTC offset for each timezone - Display live time preview for selected timezone - Update general.html to use timezone-selector instead of text input - Add script tag to base.html for widget loading Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): suppress on-demand status notification on page load Change loadOnDemandStatus(true) to loadOnDemandStatus(false) during initPluginsPage() to prevent the "on-demand status refreshed" notification from appearing every time a tab is opened or the page is navigated. The notification should only appear on explicit user refresh. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style(ui): soften notification close button appearance Replace blocky FontAwesome X icon with a cleaner SVG that has rounded stroke caps. Make the button circular, slightly transparent by default, and add smooth hover transitions for a more polished look. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): multiple security and validation improvements - color-picker.js: Ensure presets is always an array before map/filter - number-input.js: Guard against undefined options parameter - number-input.js: Sanitize and escape min/max/step HTML attributes - text-input.js: Clear custom validity in onInput to unblock form submit - timezone-selector.js: Replace legacy Europe/Belfast with Europe/London - url-input.js: Use RFC 3986 scheme pattern for protocol validation - general.html: Use |tojson filter to escape timezone value safely Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(url-input): centralize RFC 3986 protocol validation Extract protocol normalization into reusable normalizeProtocols() helper function that validates against RFC 3986 scheme pattern. Apply consistently in render, validate, and onInput to ensure protocols like "git+ssh", "android-app" are properly handled everywhere. Also lowercase protocol comparison in isValidUrl(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(timezone-selector): use hidden input for form submission Replace direct select name attribute with a hidden input pattern to ensure timezone value is always properly serialized in form submissions. The hidden input is synced on change and setValue calls. This matches the pattern used by other widgets and ensures HTMX json-enc properly captures the value. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(general): preserve timezone dropdown value after save Add inline script to sync the timezone select with the hidden input value after form submission. This prevents the dropdown from visually resetting to the old value while the save has actually succeeded. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): preserve timezone selection across form submission Use before-request handler to capture the selected timezone value before HTMX processes the form, then restore it in after-request. This is more robust than reading from the hidden input which may also be affected by form state changes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): add HTMX protection to timezone selector Add global HTMX event listeners in the timezone-selector widget that preserve the selected value across any form submissions. This is more robust than form-specific handlers as it protects the widget regardless of how/where forms are submitted. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * debug(widgets): add logging and prevent timezone widget re-init Add debug logging and guards to prevent the timezone widget from being re-initialized after it's already rendered. This should help diagnose why the dropdown is reverting after save. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * debug: add console logging to timezone HTMX protection * debug: add onChange logging to trace timezone selection * fix(widgets): use selectedIndex to force visual update in timezone dropdown The browser's select.value setter sometimes doesn't trigger a visual update when optgroup elements are present. Using selectedIndex instead forces the browser to correctly update the visible selection. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): force browser repaint on timezone dropdown restore Adding display:none/reflow/display:'' pattern to force browser to visually update the select element after changing selectedIndex. Increased timeout to 50ms for reliability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(widgets): remove debug logging from timezone selector Clean up console.log statements that were used for debugging the timezone dropdown visual update issue. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): improve HTMX after-request handler in general settings - Parse xhr.responseText with JSON.parse in try/catch instead of using nonstandard responseJSON property - Check xhr.status for 2xx success range - Show error notification for non-2xx responses - Default to safe fallback values if JSON parsing fails Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): add input sanitization and timezone validation - Sanitize minLength/maxLength in text-input.js to prevent attribute injection (coerce to integers, validate range) - Update Europe/Kiev to Europe/Kyiv (canonical IANA identifier) - Validate timezone currentValue against TIMEZONE_GROUPS before rendering Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): correct error message fallback in HTMX after-request handler Initialize message to empty string so error responses can use the fallback 'Failed to save settings' when no server message is provided. Previously, the truthy default 'Settings saved' would always be used. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): add constraint normalization and improve value validation - text-input: normalize minLength/maxLength so maxLength >= minLength - timezone-selector: validate setValue input against TIMEZONE_GROUPS - timezone-selector: sync hidden input to actual selected value - timezone-selector: preserve empty selections across HTMX requests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(widgets): simplify HTMX restore using select.value and dispatch change event Replace selectedIndex manipulation with direct value assignment for cleaner placeholder handling, and dispatch change event to refresh timezone preview. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
d0ad2031c8 |
fix(ui): wrap plugin tabs to new lines instead of scrolling (#201)
* fix(ui): wrap plugin tabs to new lines instead of scrolling Change plugin tabs row from overflow-x-auto to flex-wrap so that when many plugins are installed, tabs break to new lines instead of becoming smaller or requiring horizontal scrolling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): use gap-x instead of space-x for proper wrapped row alignment Switch from space-x-* to gap-x-* utilities so wrapped rows align correctly without indentation on subsequent lines. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): add missing flex-wrap and gap utilities to CSS The project uses hand-written Tailwind-like CSS, not actual Tailwind. Added missing utility classes needed for plugin tabs wrapping: - flex-wrap - gap-x-4, gap-x-6, gap-x-8, gap-y-2 - lg:gap-x-6, xl:gap-x-8 responsive variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): apply flex-wrap to system tabs row Apply the same wrapping behavior to the system tabs row (Overview, General, WiFi, etc.) so they also wrap to new lines on smaller viewports instead of scrolling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): constrain tab container width to enable flex-wrap Add max-w-full and overflow-hidden to tab row containers to properly constrain their width, allowing flex-wrap to work correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): remove overflow-hidden that was hiding tabs Revert the max-w-full overflow-hidden approach as it was hiding content. Keep both rows using flex-wrap with gap utilities. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Add custom-leagues widget support for soccer plugin - Add server-side template rendering for x-widget="custom-leagues" - Renders table with Name, League Code, Priority, Enabled columns - Includes inline JavaScript for add/remove row functionality - Uses indexed field naming for proper array serialization - Shows common ESPN league codes as hint This enables the soccer scoreboard plugin's custom leagues feature to work properly in the web UI. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): reduce tab gap spacing for tighter layout Reduce horizontal gap between tabs from gap-x-4/6/8 to gap-x-2/3/4 for a more compact appearance. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(widget): Replace custom-leagues with generic array-table widget - Add generic array-table widget that reads columns from schema - Support x-columns to specify which columns to display - Auto-detect columns from items.properties if x-columns not specified - Remove hardcoded custom-leagues implementation - Any plugin can now use x-widget: "array-table" for array-of-objects Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): use data attributes for array table button to avoid JSON escaping issues Move JSON blobs (item_properties and display_columns) from inline onclick to data-* attributes with proper HTML entity escaping via Jinja's |e filter. Update addArrayTableRow() to read and parse these data attributes. This fixes HTML attribute breakage caused by tojson emitting double quotes inside the onclick attribute value. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): update Add button state when array table rows change Add updateAddButtonState() helper that toggles the Add button's disabled attribute and opacity based on current row count vs maxItems. Called after addArrayTableRow() and removeArrayTableRow(), and also on page load to ensure correct initial state. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ui): add try/catch for JSON parsing in addArrayTableRow Wrap JSON.parse calls for data-item-properties and data-display-columns in try/catch blocks with fallback to {} and [] respectively. Logs error with raw attribute values to help debug malformed JSON. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(array-table): Fix getValue input name validation and setValue Add button state sync - Fix getValue to use early-continue guard preventing errors on inputs without names - Add updateAddButtonState call in setValue to refresh Add button state after repopulating rows * fix(ui): make Configure button larger than Uninstall in plugin manager Swapped button sizes in installed plugins section - Configure button is now the largest (flex-2), Update is medium (flex-1), and Uninstall is smallest (no flex class). This prioritizes the Configure action over Uninstall. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(ui): correct forEach continue and plugin button flex sizing - Replace invalid continue with return in array-table forEach callback - Remove redundant hidden input type check in array-table getValue - Fix plugin button sizing using inline flex styles instead of invalid flex-2 class - Configure button now properly sized at flex: 2, Update and Uninstall at flex: 1 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor(ui): reorganize plugin buttons into two-row layout Configure button now takes full width on first row, while Update and Uninstall buttons share the second row evenly. This makes Configure more prominent and separates destructive actions to a second row. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(ui): override inline-flex on Configure button to enable full width The .btn class uses display: inline-flex which prevents w-full from working. Added inline style to override with display: flex and width: 100% so the Configure button properly takes the full width of its row. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(ui): use inline styles for plugin action buttons layout Replace Tailwind classes with explicit inline styles to ensure proper two-row layout for plugin action buttons. Configure button on first row at full width, Update and Uninstall sharing second row evenly. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
23ada60544 |
Fix/plugins manager syntax error (#192)
* chore: Update basketball-scoreboard submodule for odds font fix * fix(plugins): Add missing closing brace in file-upload widget if block Fixed syntax error where the if statement starting at line 2949 was missing its closing brace before the else if statement. This caused 'Unexpected token else' error at line 3257 when parsing the loadCustomHtmlWidget function. The fix adds the missing closing brace at line 3048 to properly close the if block before the else if chain continues. * fix(plugins): Resolve unmatched else if syntax error in plugins_manager.js - Fixed indentation of else if chain for custom-feeds widget (line 3203) - Fixed indentation of final else block (line 3240) - Added missing closing brace to properly close array handling block (line 3257) - Resolves 'Unexpected token else' syntax error at line 3257 - Allows plugin store to load correctly * fix: Update plugins_manager.js cache-busting version to force reload of syntax fix --------- Co-authored-by: Chuck <chuck@example.com> |
||
|
|
71584d4361 |
Feature/widget registry system (#190)
* chore: Update basketball-scoreboard submodule for odds font fix
* feat(widgets): Add widget registry system for plugin configuration forms
- Create core widget registry system (registry.js, base-widget.js)
- Extract existing widgets to separate modules:
- file-upload.js: Image upload with drag-and-drop, preview, delete, scheduling
- checkbox-group.js: Multi-select checkboxes for array fields
- custom-feeds.js: Table-based RSS feed editor with logo uploads
- Implement plugin widget loading system (plugin-loader.js)
- Add comprehensive documentation (widget-guide.md, README.md)
- Include example custom widget (example-color-picker.js)
- Maintain backwards compatibility with existing plugins
- All widget handlers available globally for existing functionality
This enables:
- Reusable UI components for plugin configuration forms
- Third-party plugins to create custom widgets without modifying LEDMatrix
- Modular widget architecture for future enhancements
Existing plugins (odds-ticker, static-image, news) continue to work without changes.
* fix(widgets): Security and correctness fixes for widget system
- base-widget.js: Fix escapeHtml to always escape (coerce to string first)
- base-widget.js: Add sanitizeId helper for safe DOM ID usage
- base-widget.js: Use DOM APIs in showError instead of innerHTML
- checkbox-group.js: Normalize types in setValue for consistent comparison
- custom-feeds.js: Implement setValue with full row creation logic
- example-color-picker.js: Validate hex colors before using in style attributes
- file-upload.js: Replace innerHTML with DOM creation to prevent XSS
- file-upload.js: Preserve open schedule editors when updating image list
- file-upload.js: Normalize types when filtering deleted files
- file-upload.js: Sanitize imageId in openImageSchedule and all schedule handlers
- file-upload.js: Fix max-files check order and use allowed_types from config
- README.md: Add security guidance for ID sanitization in examples
* fix(widgets): Additional security and error handling improvements
- scripts/update_plugin_repos.py: Add explicit UTF-8 encoding and proper error handling for file operations
- scripts/update_plugin_repos.py: Fix git fetch/pull error handling with returncode checks and specific exception types
- base-widget.js: Guard notify method against undefined/null type parameter
- file-upload.js: Remove inline handlers from schedule template, use addEventListener with data attributes
- file-upload.js: Update hideUploadProgress to show dynamic file types from config instead of hardcoded list
- README.md: Update Color Picker example to use sanitized fieldId throughout
* fix(widgets): Update Slider example to use sanitized fieldId
- Add sanitizeId helper to Slider example render, getValue, and setValue methods
- Use sanitizedFieldId for all DOM IDs and query selectors
- Maintain consistency with Color Picker example pattern
* fix(plugins_manager): Move configurePlugin and togglePlugin to top of file
- Move configurePlugin and togglePlugin definitions to top level (after uninstallPlugin)
- Ensures these critical functions are available immediately when script loads
- Fixes 'Critical functions not available after 20 attempts' error
- Functions are now defined before any HTML rendering checks
* fix(plugins_manager): Fix checkbox state saving using querySelector
- Add escapeCssSelector helper function for safe CSS selector usage
- Replace form.elements[actualKey] with form.querySelector for boolean fields
- Properly handle checkbox checked state using element.checked property
- Fix both schema-based and schema-less boolean field processing
- Ensures checkboxes with dot notation names (nested fields) work correctly
Fixes issue where checkbox states were not properly saved when field names
use dot notation (e.g., 'display.scroll_enabled'). The form.elements
collection doesn't reliably handle dot notation in bracket notation access.
* fix(base.html): Fix form element lookup for dot notation field names
- Add escapeCssSelector helper function (both as method and standalone)
- Replace form.elements[key] with form.querySelector for element type detection
- Fixes element lookup failures when field names use dot notation
- Ensures checkbox and multi-select skipping logic works correctly
- Applies fix to both Alpine.js method and standalone function
This complements the fix in plugins_manager.js to ensure all form
element lookups handle nested field names (e.g., 'display.scroll_enabled')
reliably across the entire web interface.
* fix(plugins_manager): Add race condition protection to togglePlugin
- Initialize window._pluginToggleRequests map for per-plugin request tokens
- Generate unique token for each toggle request to track in-flight requests
- Disable checkbox and wrapper UI during request to prevent overlapping toggles
- Add visual feedback with opacity and pointer-events-none classes
- Verify token matches before applying response updates (both success and error)
- Ignore out-of-order responses to preserve latest user intent
- Clear token and re-enable UI after request completes
Prevents race conditions when users rapidly toggle plugins, ensuring
only the latest toggle request's response affects the UI state.
* refactor(escapeCssSelector): Use CSS.escape() for better selector safety
- Prefer CSS.escape() when available for proper CSS selector escaping
- Handles edge cases: unicode characters, leading digits, and spec compliance
- Keep regex-based fallback for older browsers without CSS.escape support
- Update all three instances: plugins_manager.js and both in base.html
CSS.escape() is the standard API for escaping CSS selectors and provides
more robust handling than custom regex, especially for unicode and edge cases.
* fix(plugins_manager): Fix syntax error - missing closing brace for file-upload if block
- Add missing closing brace before else-if for checkbox-group widget
- Fixes 'Unexpected token else' error at line 3138
- The if block for file-upload widget (line 3034) was missing its closing brace
- Now properly structured: if (file-upload) { ... } else if (checkbox-group) { ... }
* fix(plugins_manager): Fix indentation in file-upload widget if block
- Properly indent all code inside the file-upload if block
- Fix template string closing brace indentation
- Ensures proper structure: if (file-upload) { ... } else if (checkbox-group) { ... }
- Resolves syntax error at line 3138
* fix(plugins_manager): Skip checkbox-group [] inputs to prevent config leakage
- Add skip logic for keys ending with '[]' in handlePluginConfigSubmit
- Prevents checkbox-group bracket notation inputs from leaking into config
- Checkbox-group widgets emit name="...[]" checkboxes plus a _data JSON field
- The _data field is already processed correctly, so [] inputs are redundant
- Prevents schema validation failures and extra config keys
The checkbox-group widget creates:
1. Individual checkboxes with name="fullKey[]" (now skipped)
2. Hidden input with name="fullKey_data" containing JSON array (processed)
3. Sentinel hidden input with name="fullKey[]" and empty value (now skipped)
* fix(plugins_manager): Normalize string booleans when checkbox input is missing
- Fix boolean field processing to properly normalize string booleans in fallback path
- Prevents "false"/"0" from being coerced to true when checkbox element is missing
- Handles common string boolean representations: 'true', 'false', '1', '0', 'on', 'off'
- Applies to both schema-based (lines 2386-2400) and schema-less (lines 2423-2433) paths
When a checkbox element cannot be found, the fallback logic now:
1. Checks if value is a string and normalizes known boolean representations
2. Treats undefined/null as false
3. Coerces other types to boolean using Boolean()
This ensures string values like "false" or "0" are correctly converted to false
instead of being treated as truthy non-empty strings.
* fix(base.html): Improve escapeCssSelector fallback to match CSS.escape behavior
- Handle leading digits by converting to hex escape (e.g., '1' -> '\0031 ')
- Handle leading whitespace by converting to hex escape (e.g., ' ' -> '\0020 ')
- Escape internal spaces as '\ ' (preserving space in hex escapes)
- Ensures trailing space after hex escapes per CSS spec
- Applies to both Alpine.js method and standalone function
The fallback now better matches CSS.escape() behavior for older browsers:
1. Escapes leading digits (0-9) as hex escapes with trailing space
2. Escapes leading whitespace as hex escapes with trailing space
3. Escapes all special characters as before
4. Escapes internal spaces while preserving hex escape format
This prevents selector injection issues with field names starting with digits
or whitespace, matching the standard CSS.escape() API behavior.
---------
Co-authored-by: Chuck <chuck@example.com>
|
||
|
|
4a63ff87cb |
Feature/soccer scroll support (#186)
* fix: Use plugin.modes instead of manifest.json for available modes - Display controller now checks plugin_instance.modes first before falling back to manifest - This allows plugins to dynamically provide modes based on enabled leagues - Fixes issue where disabled leagues (WNBA, NCAAW) appeared in available modes - Plugins can now control their available modes at runtime based on config * fix: Handle permission errors when removing plugin directories - Added _safe_remove_directory() method to handle permission errors gracefully - Fixes permissions on __pycache__ directories before removal - Updates uninstall_plugin() and install methods to use safe removal - Resolves [Errno 13] Permission denied errors during plugin install/uninstall * debug(display): Change FPS check logging from debug to info level - Change FPS check log from DEBUG to INFO to help diagnose scrolling FPS issues - Add active_mode to log message for clarity - Helps identify if plugins are being detected for high-FPS mode * debug(display): Add logging for display_interval in both FPS loops - Log display_interval when entering high-FPS and normal loops - Shows expected FPS for high-FPS mode - Helps diagnose why news ticker shows 50 FPS despite high-FPS detection * feat: Update soccer-scoreboard submodule with scroll display support - Submodule now includes full feature parity with football-scoreboard - Granular display modes for 8 leagues (24 total modes) - Scroll display mode with game_renderer.py and scroll_display.py - League registry system with enabled state filtering - Modernized config_schema.json with per-league scroll settings - League-aware logo caching to prevent collisions - Pillow 8.x compatibility for image resampling Submodule branch: feature/football-feature-parity Commit: e22a16d * style(web): Update plugin button colors and reorganize documentation - Change update button color to yellow-600 in installed plugins section to match plugin config page - Change refresh plugins button color to blue-600 to match restart display button - Move DEVELOPMENT.md and MIGRATION_GUIDE.md from root to docs/ directory - Remove IMPACT_EXPLANATION.md and MERGE_CONFLICT_RESOLUTION_PLAN.md --------- Co-authored-by: Chuck <chuck@example.com> |
||
|
|
0f4dbb6c1a |
Feature/one shot installer (#178)
* fix(plugins): Remove compatible_versions requirement from single plugin install
Remove compatible_versions from required fields in install_from_url method
to match install_plugin behavior. This allows installing plugins from URLs
without manifest version requirements, consistent with store plugin installation.
* fix(7-segment-clock): Update submodule with separator and spacing fixes
* fix(plugins): Add onchange handlers to existing custom feed inputs
- Add onchange handlers to key and value inputs for existing patternProperties fields
- Fixes bug where editing existing custom RSS feeds didn't save changes
- Ensures hidden JSON input field is updated when users edit feed entries
- Affects all plugins using patternProperties (custom_feeds, feed_logo_map, etc.)
* Add array-of-objects widget support to web UI
- Add support for rendering arrays of objects in web UI (for custom_feeds)
- Implement add/remove/update functions for array-of-objects widgets
- Support file-upload widgets within array items
- Update form data handling to support array JSON data fields
* Update plugins_manager.js cache-busting version
Update version parameter to force browser to load new JavaScript with array-of-objects widget support.
* Fix: Move array-of-objects detection before file-upload/checkbox checks
Move the array-of-objects widget detection to the top of the array handler so it's checked before file-upload and checkbox-group widgets. This ensures custom_feeds is properly detected as an array of objects.
* Update cache-busting version for array-of-objects fix
* Remove duplicate array-of-objects check
* Update cache version again
* Add array-of-objects widget support to server-side template
Add detection and rendering for array-of-objects in the Jinja2 template (plugin_config.html).
This enables the custom_feeds widget to display properly with name, URL, enabled checkbox, and logo upload fields.
The widget is detected by checking if prop.items.type == 'object' && prop.items.properties,
and is rendered before the file-upload widget check.
* Use window. prefix for array-of-objects JavaScript functions
Explicitly use window.addArrayObjectItem, window.removeArrayObjectItem, etc.
in the template to ensure the functions are accessible from inline event handlers.
Also add safety checks to prevent errors if functions aren't loaded yet.
* Fix syntax error: Missing indentation for html += in array else block
The html += statement was outside the else block, causing a syntax error.
Fixed by properly indenting it inside the else block.
* Update cache version for syntax fix
* Add debug logging to diagnose addArrayObjectItem availability
* Fix: Wrap array-of-objects functions in window check and move outside IIFE
Ensure functions are available globally by wrapping them in a window check
and ensuring they're defined outside any IIFE scope. Also fix internal
function calls to use window.updateArrayObjectData for consistency.
* Update cache version for array-of-objects fix
* Move array-of-objects functions outside IIFE to make them globally available
The functions were inside the IIFE scope, making them inaccessible from
inline event handlers. Moving them outside the IIFE ensures they're
available on window when the script loads.
* Update cache version for IIFE fix
* Fix: Add array-of-objects functions after IIFE ends
The functions were removed from inside the IIFE but never added after it.
Also removed orphaned code that was causing syntax errors.
* Update cache version for array-of-objects fix
* Fix: Remove all orphaned code and properly add array-of-objects functions after IIFE
* Add array-of-objects functions after IIFE ends
These functions must be outside the IIFE to be accessible from inline
event handlers in the server-rendered template.
* Update cache version for syntax fix
* Fix syntax error: Add missing closing brace for else block
* Update cache version for syntax fix
* Replace complex array-of-objects widget with simple table interface
- Replace nested array-of-objects widget with clean table interface
- Table shows: Name, URL, Logo (with upload), Enabled checkbox, Delete button
- Fix file-upload widget detection order to prevent breaking static-image plugin
- Add simple JavaScript functions for add/remove rows and logo upload
- Much more intuitive and easier to use
* Add simple table interface for custom feeds
- Replace complex array-of-objects widget with clean table
- Table columns: Name, URL, Logo (upload), Enabled checkbox, Delete
- Use dot notation for form field names (feeds.custom_feeds.0.name)
- Add JavaScript functions for add/remove rows and logo upload
- Fix file-upload detection order to prevent breaking static-image plugin
* Fix custom feeds table issues
- Fix JavaScript error in removeCustomFeedRow (get tbody before removing row)
- Improve array conversion logic to handle nested paths like feeds.custom_feeds
- Add better error handling and debug logging for array conversion
- Ensure dicts with numeric keys are properly converted to arrays before validation
* Add fallback fix for feeds.custom_feeds dict-to-array conversion
- Add explicit fallback conversion for feeds.custom_feeds if fix_array_structures misses it
- This ensures the dict with numeric keys is converted to an array before validation
- Logo field is already optional in schema (not in required array)
* feat(web): Add checkbox-group widget support for plugin config arrays
Add server-side rendering support for checkbox-group widget in plugin
configuration forms. This allows plugins to use checkboxes for multi-select
array fields instead of comma-separated text inputs.
The implementation:
- Checks for x-widget: 'checkbox-group' in schema
- Renders checkboxes for each enum item in items.enum
- Supports custom labels via x-options.labels
- Works with any plugin that follows the pattern
Already used by:
- ledmatrix-news plugin (enabled_feeds)
- odds-ticker plugin (enabled_leagues)
* feat(install): Add one-shot installation script
- Create comprehensive one-shot installer with robust error handling
- Includes network checks, disk space validation, and retry logic
- Handles existing installations gracefully (idempotent)
- Updates README with quick install command prominently featured
- Manual installation instructions moved to collapsible section
The script provides explicit error messages and never fails silently.
All prerequisites are validated before starting installation.
* fix: Remove accidental plugins/7-segment-clock submodule entry
Remove uninitialized submodule 'plugins/7-segment-clock' that was
accidentally included. This submodule is not related to the one-shot
installer feature and should not be part of this PR.
- Remove submodule entry from .gitmodules
- Remove submodule from git index
- Clean up submodule configuration
* fix(array-objects): Fix schema lookup, reindexing, and disable file upload
Address PR review feedback for array-of-objects helpers:
1. Schema resolution: Use getSchemaProperty() instead of manual traversal
- Fixes nested array-of-objects schema lookup (e.g., news.custom_feeds)
- Now properly descends through .properties for nested objects
2. Reindexing: Replace brittle regex with targeted patterns
- Only replace index in bracket notation [0], [1], etc. for names
- Only replace _item_<digits> pattern for IDs (not arbitrary digits)
- Use specific function parameter patterns for onclick handlers
- Prevents corruption of fieldId, pluginId, or other numeric values
3. File upload: Disable widget until properly implemented
- Hide/disable upload button with clear message
- Show existing logos if present but disable upload functionality
- Prevents silent failures when users attempt to upload files
- Added TODO comments for future implementation
Also fixes exit code handling in one-shot-install.sh to properly capture
first_time_install.sh exit status before error trap fires.
* fix(security): Fix XSS vulnerability in handleCustomFeedLogoUpload
Replace innerHTML usage with safe DOM manipulation using createElement
and setAttribute to prevent XSS when injecting uploadedFile.path and
uploadedFile.id values.
- Clear logoCell using textContent instead of innerHTML
- Create all DOM elements using document.createElement
- Set uploadedFile.path and uploadedFile.id via setAttribute (automatically escaped)
- Properly structure DOM tree by appending elements in order
- Prevents malicious HTML/script injection through file path or ID values
* fix: Update upload button onclick when reindexing custom feed rows
Fix removeCustomFeedRow to update button onclick handlers that reference
file input IDs with _logo_<index> when rows are reindexed after deletion.
Previously, after deleting a row, the upload button's onclick still referenced
the old file input ID, causing the upload functionality to fail.
Now properly updates:
- getElementById('..._logo_<num>') patterns in onclick handlers
- Other _logo_<num> patterns in button onclick strings
- Function parameter indices in onclick handlers
This ensures upload buttons continue to work correctly after row deletion.
* fix: Make custom feeds table widget-specific instead of generic fallback
Replace generic array-of-objects check with widget-specific check for
'custom-feeds' widget to prevent hardcoded schema from breaking other
plugins with different array-of-objects structures.
Changes:
- Check for x-widget == 'custom-feeds' before rendering custom feeds table
- Add schema validation to ensure required fields (name, url) exist
- Show warning message if schema doesn't match expected structure
- Fall back to generic array input for other array-of-objects schemas
- Add comments for future generic array-of-objects support
This ensures the hardcoded custom feeds table (name, url, logo, enabled)
only renders when explicitly requested via widget type, preventing
breakage for other plugins with different array-of-objects schemas.
* fix: Add image/gif to custom feed logo upload accept attribute
Update file input accept attributes for custom feed logo uploads to include
image/gif, making it consistent with the file-upload widget which also
allows GIF images.
Updated in three places:
- Template file input (plugin_config.html)
- JavaScript addCustomFeedRow function (base.html)
- Dynamic file input creation in handleCustomFeedLogoUpload (base.html)
All custom feed logo upload inputs now accept: image/png, image/jpeg,
image/bmp, image/gif
* fix: Add hidden input for enabled checkbox to ensure false is submitted
Add hidden input with value='false' before enabled checkbox in custom feeds
table to ensure an explicit false value is sent when checkbox is unchecked.
Pattern implemented:
- Hidden input: name='enabled', value='false' (always submitted)
- Checkbox: name='enabled', value='true' (only submitted when checked)
- When unchecked: only hidden input submits (false)
- When checked: both submit, checkbox value (true) overwrites hidden
Updated in two places:
- Template checkbox in plugin_config.html (existing rows)
- JavaScript addCustomFeedRow function in base.html (new rows)
Backend verification:
- Backend (api_v3.py) handles string boolean values and converts properly
- JavaScript form processing explicitly checks element.checked, independent of this pattern
- Standard form submission uses last value when multiple values share same name
* fix: Expose renderArrayObjectItem to window for addArrayObjectItem
Fix scope issue where renderArrayObjectItem is defined inside IIFE but
window.addArrayObjectItem is defined outside, causing the function check
to always fail and fallback to degraded HTML rendering.
Problem:
- renderArrayObjectItem (line 2469) is inside IIFE (lines 796-6417)
- window.addArrayObjectItem (line 6422) is outside IIFE
- Check 'typeof renderArrayObjectItem === function' at line 6454 always fails
- Fallback code lacks file upload widgets, URL input types, descriptions, styling
Solution:
- Expose renderArrayObjectItem to window object before IIFE closes
- Function maintains closure access to escapeHtml and other IIFE-scoped functions
- Newly added items now have full functionality matching initially rendered items
* fix: Reorder array type checks to match template order
Fix inconsistent rendering where JavaScript and Jinja template had opposite
ordering for array type checks, causing schemas with both x-widget: file-upload
AND items.type: object (like static-image) to render differently.
Problem:
- Template checks file-upload FIRST (to avoid breaking static-image plugin)
- JavaScript checked array-of-objects FIRST
- Server-rendered forms showed file-upload widget correctly
- JS-rendered forms incorrectly displayed array-of-objects table widget
Solution:
- Reorder JavaScript checks to match template order:
1. Check file-upload widget FIRST
2. Check checkbox-group widget
3. Check custom-feeds widget
4. Check array-of-objects as fallback
5. Regular array input (comma-separated)
This ensures consistent rendering between server-rendered and JS-rendered forms
for schemas that have both x-widget: file-upload AND items.type: object.
* fix: Handle None value for feeds config to prevent TypeError
Fix crash when plugin_config['feeds'] exists but is None, causing
TypeError when checking 'custom_feeds' in feeds_config.
Problem:
- When plugin_config['feeds'] exists but is None, dict.get('feeds', {})
returns None (not the default {}) because dict.get() only uses default
when key doesn't exist, not when value is None
- Line 3642's 'custom_feeds' in feeds_config raises TypeError because
None is not iterable
- This can crash the API endpoint if a plugin config has feeds: null
Solution:
- Change plugin_config.get('feeds', {}) to plugin_config.get('feeds') or {}
to ensure feeds_config is always a dict (never None)
- Add feeds_config check before 'in' operator for extra safety
This ensures the code gracefully handles feeds: null in plugin configuration.
* fix: Add default value for AVAILABLE_SPACE to prevent TypeError
Fix crash when df produces unexpected output that results in empty
AVAILABLE_SPACE variable, causing 'integer expression expected' error.
Problem:
- df may produce unexpected output format (different locale, unusual
filesystem name spanning lines, or non-standard df implementation)
- While '|| echo "0"' handles pipeline failures, it doesn't trigger if
awk succeeds but produces no output (empty string)
- When AVAILABLE_SPACE is empty, comparison [ "$AVAILABLE_SPACE" -lt 500 ]
fails with 'integer expression expected' error
- With set -e, this causes script to exit unexpectedly
Solution:
- Add AVAILABLE_SPACE=${AVAILABLE_SPACE:-0} before comparison to ensure
variable always has a numeric value (defaults to 0 if empty)
- This gracefully handles edge cases where df/awk produces unexpected output
* fix: Wrap debug console.log in debug flag check
Fix unconditional debug logging that outputs internal implementation
details to browser console for all users.
Problem:
- console.log('[ARRAY-OBJECTS] Functions defined on window:', ...)
executes unconditionally when page loads
- Outputs debug information about function availability to all users
- Appears to be development/debugging code inadvertently included
- Noisy console output in production
Solution:
- Wrap console.log statement in _PLUGIN_DEBUG_EARLY check to only
output when pluginDebug localStorage flag is enabled
- Matches pattern used elsewhere in the file for debug logging
- Debug info now only visible when explicitly enabled via
localStorage.setItem('pluginDebug', 'true')
* fix: Expose getSchemaProperty, disable upload widget, handle bracket notation arrays
Multiple fixes for array-of-objects and form processing:
1. Expose getSchemaProperty to window (plugins_manager.js):
- getSchemaProperty was defined inside IIFE but needed by global functions
- Added window.getSchemaProperty = getSchemaProperty before IIFE closes
- Updated window.addArrayObjectItem to use window.getSchemaProperty
- Fixes ReferenceError when dynamically adding array items
2. Disable upload widget for custom feeds (plugin_config.html):
- File input and Upload button were still active but should be disabled
- Removed onchange/onclick handlers, added disabled and aria-disabled
- Added visible disabled styling and tooltip
- Existing logos continue to display but uploads are prevented
- Matches PR objectives to disable upload until fully implemented
3. Handle bracket notation array fields (api_v3.py):
- checkbox-group uses name="field_name[]" which sends multiple values
- request.form.to_dict() collapses duplicate keys (only keeps last value)
- Added handling to detect fields ending with "[]" before to_dict()
- Use request.form.getlist() to get all values, combine as comma-separated
- Processed before existing array index field handling
- Fixes checkbox-group losing all but last selected value
* fix: Remove duplicate submit handler to prevent double POSTs
Remove document-level submit listener that conflicts with handlePluginConfigSubmit,
causing duplicate form submissions with divergent payloads.
Problem:
- handlePluginConfigSubmit correctly parses JSON from _data fields and maps to
flatConfig[baseKey] for patternProperties and array-of-objects
- Document-level listener (line 5368) builds its own config without understanding
_data convention and posts independently via savePluginConfiguration
- Every submit now sends two POSTs with divergent payloads:
- First POST: Correct structure with parsed _data fields
- Second POST: Incorrect structure with raw _data fields, missing structure
- Arrays-of-objects and patternProperties saved incorrectly in second request
Solution:
- Remove document-level submit listener for #plugin-config-form
- Rely solely on handlePluginConfigSubmit which is already attached to the form
- handlePluginConfigSubmit properly handles all form-to-config conversion including:
- _data field parsing (JSON from hidden fields)
- Type-aware conversion using schema
- Dot notation to nested object conversion
- PatternProperties and array-of-objects support
Note: savePluginConfiguration function remains for use by JSON editor saves
* fix: Use indexed names for checkbox-group to work with existing parser
Change checkbox-group widget to use indexed field names instead of bracket
notation, so the existing indexed field parser correctly handles multiple
selected values.
Problem:
- checkbox-group uses name="{{ full_key }}[]" which requires bracket
notation handling in backend
- While bracket notation handler exists, using indexed names is more robust
and leverages existing well-tested indexed field parser
- Indexed field parser already handles fields like "field_name.0",
"field_name.1" correctly
Solution:
- Template: Change name="{{ full_key }}[]" to name="{{ full_key }}.{{
loop.index0 }}"
- JavaScript: Update checkbox-group rendering to use name="."
- Backend indexed field parser (lines 3364-3388) already handles this pattern:
- Detects fields ending with numeric indices (e.g., ".0", ".1")
- Groups them by base_path and sorts by index
- Combines into array correctly
This ensures checkbox-group values are properly preserved when multiple
options are selected, working with the existing schema-based parsing system.
* fix: Set values from item data in fallback array-of-objects rendering
Fix fallback code path for rendering array-of-objects items to properly
set input values from existing item data, matching behavior of proper
renderArrayObjectItem function.
Problem:
- Fallback code at lines 3078-3091 and 6471-6486 creates input elements
without setting values from existing item data
- Text inputs have no value attribute set
- Checkboxes have no checked attribute computed from item properties
- Users would see empty form fields instead of existing configuration data
- Proper renderArrayObjectItem function correctly sets values (line 2556)
Solution:
- Extract propValue from item data: item[propKey] with schema default fallback
- For text inputs: Set value attribute with HTML-escaped propValue
- For checkboxes: Set checked attribute based on propValue truthiness
- Add inline HTML escaping for XSS prevention (since fallback code may
run outside IIFE scope where escapeHtml function may not be available)
This ensures fallback rendering displays existing data correctly when
window.renderArrayObjectItem is not available.
* fix: Remove extra closing brace breaking if/else chain
Remove stray closing brace at line 3127 that was breaking the if/else chain
before the 'else if (prop.enum)' branch, causing 'Unexpected token else'
syntax error.
Problem:
- Extra '}' at line 3127 closed the prop.type === 'array' block prematurely
- This broke the if/else chain, causing syntax error when parser reached
'else if (prop.enum)' at line 3128
- Structure was: } else if (array) { ... } } } else if (enum) - extra brace
Solution:
- Removed the extra closing brace at line 3127
- Structure now correctly: } else if (array) { ... } } else if (enum)
- Verified with Node.js syntax checker - no errors
* fix: Remove local logger assignments to prevent UnboundLocalError
Remove all local logger assignments inside save_plugin_config function that
were shadowing the module-level logger, causing UnboundLocalError when nested
helpers like normalize_config_values() or debug checks reference logger before
those assignments run.
Problem:
- Module-level logger exists at line 13: logger = logging.getLogger(__name__)
- Multiple local assignments inside save_plugin_config (lines 3361, 3401, 3421,
3540, 3660, 3977, 4093, 4118) make logger a local variable for entire function
- Python treats logger as local for entire function scope when any assignment
exists, causing UnboundLocalError if logger is used before assignments
- Nested helpers like normalize_config_values() or debug checks that reference
logger before local assignments would fail
Solution:
- Removed all local logger = logging.getLogger(__name__) assignments in
save_plugin_config function
- Use module-level logger directly throughout the function
- Removed redundant import logging statements that were only used for logger
- This ensures logger is always available and references the module-level logger
All logger references now use the module-level logger without shadowing.
* fix: Fix checkbox-group serialization and array-of-objects key leakage
Multiple fixes for array-of-objects and checkbox-group widgets:
1. Fix checkbox-group serialization (JS and template):
- Changed from indexed names (categories.0, categories.1) to _data pattern
- Added updateCheckboxGroupData() function to sync selected values
- Hidden input stores JSON array of selected enum values
- Checkboxes use data-checkbox-group and data-option-value attributes
- Fixes issue where config.categories became {0: true, 1: true} instead of ['nfl', 'nba']
- Now correctly serializes to array using existing _data handling logic
2. Prevent array-of-objects per-item key leakage:
- Added skip pattern in handlePluginConfigSubmit for _item_<n>_ names
- Removed name attributes from per-item inputs in renderArrayObjectItem
- Per-item inputs now rely solely on hidden _data field
- Prevents feeds_item_0_name from leaking into flatConfig
3. Add type coercion to updateArrayObjectData:
- Consults itemsSchema.properties[propKey].type for coercion
- Handles integer and number types correctly
- Preserves string values as-is
- Ensures numeric fields in array items are stored as numbers
4. Ensure currentPluginConfig is always available:
- Updated addArrayObjectItem to check window.currentPluginConfig first
- Added error logging if schema not available
- Prevents ReferenceError when global helpers need schema
This ensures checkbox-group arrays serialize correctly and array-of-objects
per-item fields don't leak extra keys into the configuration.
* fix: Make _data field matching more specific to prevent false positives
Fix overly broad condition that matched any field containing '_data',
causing false positives and inconsistent key transformation.
Problem:
- Condition 'key.endsWith('_data') || key.includes('_data')' matches any
field containing '_data' anywhere (e.g., 'meta_data_field', 'custom_data_config')
- key.replace(/_data$/, '') only removes '_data' from end, making logic inconsistent
- Fields with '_data' in middle get matched but key isn't transformed
- If their value happens to be valid JSON, it gets incorrectly parsed
Solution:
- Remove 'key.includes('_data')' clause
- Only check 'key.endsWith('_data')' to match actual _data suffix pattern
- Ensures consistent matching: only fields ending with '_data' are treated
as JSON data fields, and only those get the suffix removed
- Prevents false positives on fields like 'meta_data_field' that happen to
contain '_data' in their name
* fix: Add HTML escaping to prevent XSS in fallback code and checkbox-group
Add proper HTML escaping for schema-derived values to prevent XSS vulnerabilities
in fallback rendering code and checkbox-group widget.
Problem:
- Fallback code in generateFieldHtml (line 3094) doesn't escape propLabel
when building HTML strings, while main renderArrayObjectItem uses escapeHtml()
- Checkbox-group widget (lines 3012-3025) doesn't escape option or label values
- While risk is limited (values come from plugin schemas), malicious plugin
schemas or untrusted schema sources could inject XSS
- Inconsistent with main renderArrayObjectItem which properly escapes
Solution:
- Added escapeHtml() calls for propLabel in fallback array-of-objects rendering
(both locations: generateFieldHtml and addArrayObjectItem fallback)
- Added escapeHtml() calls for option values in checkbox-group widget:
- checkboxId (contains option)
- data-option-value attribute
- value attribute
- label text in span
- Ensures consistent XSS protection across all rendering paths
This prevents potential XSS if plugin schemas contain malicious HTML/script
content in enum values or property titles.
* fix: Recreate one-shot install script with APT permission and non-interactive fixes
Recreate one-shot install script that was deleted, with fixes for:
1. APT permission denied errors on /tmp
2. Non-interactive mode support
Fixes:
1. Fix /tmp permissions before running first_time_install.sh:
- chmod 1777 /tmp to ensure APT can write temp files
- Set TMPDIR=/tmp explicitly
- Preserve TMPDIR when using sudo -E
2. Enable non-interactive mode:
- Pass -y flag or LEDMATRIX_ASSUME_YES=1 to first_time_install.sh
- Prevents read prompt failure at line 242 when run via curl | bash
3. Better error handling:
- Temporarily disable errexit to capture exit code
- Re-enable errexit after capturing
- Added fix_tmp_permissions() function
This resolves the 'Permission denied' errors for APT temp files and the
interactive prompt failure when running via pipe.
* fix: Pass both -y flag and env var to first_time_install.sh for non-interactive mode
Ensure first_time_install.sh runs in non-interactive mode by passing both:
1. The -y command-line flag
2. The LEDMATRIX_ASSUME_YES=1 environment variable
This is necessary because first_time_install.sh re-executes itself with sudo
if not running as root (line 131), and we need to ensure the non-interactive
flag is preserved through the re-execution.
Also added debug_install.sh diagnostic script to help troubleshoot
installation failures on the Pi.
* fix: Improve /tmp permission handling and non-interactive mode detection
Improve handling of /tmp permissions and non-interactive mode:
1. /tmp permissions fix:
- Check current permissions before attempting to fix
- Display warning when fixing incorrect permissions (2775 -> 1777)
- Verify /tmp has permissions 1777 (sticky bit + world writable)
2. Non-interactive mode detection:
- Redirect stdin from /dev/null when running via sudo to prevent
read commands from hanging when stdin is not a TTY
- Add better error message in first_time_install.sh when non-interactive
mode is detected but ASSUME_YES is not set
- Check if stdin is a TTY before attempting interactive read
This fixes the issues identified in diagnostic output:
- /tmp permissions 2775 causing APT write failures
- read -p failing when stdin is not a TTY (curl | bash)
Fixes installation failures when running one-shot install via curl | bash.
* refactor: Simplify /tmp permission handling - only fix if actually wrong
Simplify /tmp permission handling:
- Only check and fix /tmp permissions if they're actually incorrect (not preemptively)
- Remove redundant fix_tmp_permissions() call from prerequisites check
- Keep the fix inline where first_time_install.sh is executed
- When running manually, /tmp usually has correct permissions (1777) so no fix needed
This makes the script less aggressive and avoids unnecessary permission changes
when running manually, while still fixing the issue in automated scenarios.
* fix: Remove user confirmation prompts in install_wifi_monitor.sh for non-interactive mode
Make install_wifi_monitor.sh respect non-interactive mode:
1. Package installation prompt (line 48):
- Check for ASSUME_YES or LEDMATRIX_ASSUME_YES environment variable
- If set, automatically install required packages without prompting
- If stdin is not a TTY (non-interactive), also auto-install packages
- Only prompt user in true interactive mode (TTY available)
2. Continue installation prompt (line 145):
- Already checks for ASSUME_YES, but now also checks LEDMATRIX_ASSUME_YES
- Skip prompt if stdin is not a TTY
- Proceed automatically in non-interactive mode
This fixes installation failures at step 8.5 when running via one-shot
installer or with -y flag, as the script was hanging on user prompts.
* fix: Explicitly pass ASSUME_YES to install_wifi_monitor.sh and simplify package installation
Fix WiFi monitor installation failing at step 8.5:
1. Explicitly pass ASSUME_YES environment variable when calling
install_wifi_monitor.sh from first_time_install.sh to ensure
non-interactive mode is respected
2. Simplify package installation logic in install_wifi_monitor.sh:
- Use apt directly when running as root (from first_time_install.sh)
- Use sudo when running as regular user (direct script execution)
- Always install packages automatically in non-interactive mode
- Only prompt in true interactive mode (TTY available and ASSUME_YES not set)
This ensures packages are installed automatically when running via
one-shot installer or with -y flag, preventing installation failures
at step 8.5.
* refactor: Remove all prompts from install_wifi_monitor.sh - install packages automatically
Simplify WiFi monitor installation by removing all user prompts:
1. Package installation: Always install required packages automatically
- No prompt for missing packages (hostapd, dnsmasq, network-manager)
- Just install them if missing
2. Network connection warning: Remove prompt to continue
- Just display informational message and proceed
- WiFi monitor will handle AP mode automatically if no network
3. Remove ASSUME_YES environment variable passing from first_time_install.sh
- No longer needed since script has no prompts
This makes the installation completely non-interactive and simpler,
preventing any hangs or failures at step 8.5.
* fix: Address multiple issues in debug script, array rendering, and custom feeds
1. debug_install.sh: Make log path dynamic instead of hardcoded
- Compute project root from script location
- Use dynamic LOG_DIR instead of hardcoded /home/ledpi/LEDMatrix/logs/
- Works from any clone location and user
2. plugins_manager.js renderArrayObjectItem: Fix XSS and metadata issues
- HTML-escape logoValue.path in img src attribute (XSS prevention)
- Add data-file-data attribute to preserve file metadata for serialization
- Add data-prop-key attribute for proper property tracking
- Use schema-driven remove button label (x-removeLabel) with fallback to 'Remove item'
3. base.html addCustomFeedRow: Fix duplicate enabled field and hardcoded pluginId
- Remove duplicate hidden input for enabled field (checkbox alone is sufficient)
- Add pluginId parameter to function signature
- Pass pluginId to handleCustomFeedLogoUpload instead of hardcoded 'ledmatrix-news'
- Update caller in plugin_config.html to pass plugin_id
These fixes improve security (XSS prevention), functionality (metadata
preservation), and maintainability (no hardcoded values).
* fix: Make install_wifi_monitor.sh more resilient to failures
Make install_wifi_monitor.sh handle errors more gracefully:
1. Remove unnecessary sudo when running as root:
- Check EUID before using sudo for systemctl commands
- Use systemctl directly when running as root
- Use sudo only when running as regular user
2. Add error handling for package installation:
- Continue even if apt update fails (just warn)
- Continue even if apt install fails (warn and provide manual install command)
- Allow installation to continue even if packages fail
3. Make service operations more resilient:
- Remove sudo when running as root
- Allow service start to fail without exiting script
- Print warning if service fails to start
- Service will still be enabled and may start on reboot
Note: Script still uses 'set -e' but errors in critical paths are handled
with || operators to prevent exit. This prevents the script from exiting
with code 1 when called from first_time_install.sh, allowing the
installation to continue even if some WiFi-related operations fail.
* fix: Make WiFi monitor installation failure non-fatal in first_time_install.sh
Make the WiFi monitor service installation optional/non-fatal:
1. Capture exit code from install_wifi_monitor.sh but don't fail installation
2. Continue installation even if WiFi monitor installation fails
3. Provide clear messages about the failure but allow installation to proceed
4. Check for service file creation and provide helpful messages
WiFi monitor is optional functionality - the main LED Matrix installation
should succeed even if WiFi monitor setup fails (e.g., package installation
issues, service start failures, etc.). Users can install it later if needed.
This prevents the entire installation from failing at step 8.5 due to
WiFi monitor installation issues.
* fix: Use JSON encoding for bracket-notation arrays and add sentinel for clearing
Fix bracket-notation array handling to prevent data loss:
1. Use JSON encoding instead of comma-join (lines 3358-3359):
- Comma-join breaks if option values contain commas
- Switch to json.dumps() to encode array values as JSON strings
- _parse_form_value_with_schema() already handles JSON arrays correctly
- Preserves values with commas, special characters, etc.
2. Add sentinel hidden input for clearing arrays:
- Add hidden input with name="field[]" value="" in checkbox-group template
- Ensures field is always submitted, even when all checkboxes unchecked
- Backend filters out sentinel empty strings to detect empty array
- Allows users to clear array to [] by unchecking all options
3. Update backend to handle sentinel:
- Filter out sentinel empty strings from bracket notation values
- Empty array (all unchecked) is represented as "[]" JSON string
- Properly handles both sentinel-only (empty array) and sentinel+values cases
This fixes data loss when:
- Option values contain commas (comma-join corruption)
- All checkboxes are unchecked (field omitted from form, can't clear to [])
* fix: Harden upload flow - HTTP status check, path normalization, property assignment
Fix three security and reliability issues in upload flow:
1. Check HTTP status before calling response.json():
- Prevents JSON parsing errors on non-2xx responses
- Properly handles error responses with status codes
- Returns error text if available for better debugging
- Prevents masking of HTTP errors
2. Normalize uploadedFile.path before using in img src:
- Remove leading slashes with replace(/^\/+/, '')
- Add single leading slash for image src
- Prevents //host/odd paths that could cause security issues
- Ensures consistent path format
3. Replace string-based handlers with property assignment:
- Replace setAttribute('onchange', ...) with addEventListener('change', ...)
- Replace setAttribute('onclick', ...) with addEventListener('click', ...)
- Refactor addCustomFeedRow to use DOM manipulation instead of innerHTML
- Prevents injection vulnerabilities from string interpolation
- Uses property assignment (img.src, input.name, input.value) instead of setAttribute where appropriate
These changes improve security by eliminating XSS injection surfaces
and improve reliability by properly handling HTTP errors and path formats.
* fix: Add bracket notation to checkbox-group input names
The backend expects checkbox groups to submit with bracket notation
(request.form.getlist("<field>[]")), but the templates were rendering
checkboxes without the "[]" suffix in the name attribute.
Changes:
1. Add name="{{ full_key }}[]" to checkbox inputs in plugin_config.html
2. Add name="${fullKey}[]" to checkbox inputs in plugins_manager.js
This ensures:
- Checked checkboxes submit their values with the bracket notation
- Backend can use request.form.getlist("<field>[]") to collect all values
- Sentinel hidden input (already using bracket notation) works correctly
- Backend bracket_array_fields logic receives and processes the array values
The sentinel hidden input ensures the field is always submitted (even
when all checkboxes are unchecked), allowing the backend to detect and
set empty arrays correctly.
* fix: Swap order of enabled checkbox and hidden input in custom-feeds
The hidden input with value="false" was rendered before the checkbox,
causing request.form.to_dict() to use the hidden input's value instead
of the checkbox's "true" value when checked.
Fix by rendering the checkbox first, then the hidden fallback input.
This ensures that when the checkbox is checked, its "true" value
overwrites the hidden input's "false" value in request.form.to_dict().
The hidden input still serves as a fallback to ensure "false" is
submitted when the checkbox is unchecked (since unchecked checkboxes
don't submit a value).
* fix: Enable upload buttons for existing custom feed rows in template
The template was rendering disabled upload buttons for existing custom
feed rows with the message "Logo upload for custom feeds is not yet
implemented", while the JavaScript addCustomFeedRow function creates
working upload buttons for newly added rows. This created confusing UX
where users saw disabled buttons on existing feeds but working buttons
on newly added feeds.
Since handleCustomFeedLogoUpload is fully implemented and functional,
enable the upload buttons in the template to match the JavaScript
behavior:
1. Remove disabled and aria-disabled attributes from file input
2. Remove disabled, aria-disabled, misleading title, and update button
styling to match working buttons (remove cursor-not-allowed and
opacity-50, add hover:bg-gray-300)
3. Add onchange handler to file input calling handleCustomFeedLogoUpload
4. Add onclick handler to button to trigger file input click
This ensures consistent UX across existing and newly added custom feed
rows, with all upload buttons functional.
* fix: Expose escapeHtml to window object for use by global functions
The escapeHtml function is defined inside the IIFE (at line 5445) but is
called at line 6508 from within window.addArrayObjectItem, which is
defined outside the IIFE (starting at line 6465). Since escapeHtml is
not exposed to the window object (unlike renderArrayObjectItem and
getSchemaProperty which are exposed at lines 6457-6458), the fallback
code path throws a ReferenceError: escapeHtml is not defined when
window.renderArrayObjectItem is unavailable.
Fix by exposing escapeHtml to the window object alongside
renderArrayObjectItem and getSchemaProperty, ensuring the fallback code
in window.addArrayObjectItem can safely call escapeHtml when the primary
rendering function fails to load.
This prevents users from being unable to add new items to array-of-objects
fields when the primary rendering function is unavailable.
* fix: Escape single quotes in checkbox-group JSON value attribute
The hidden input for checkbox-group uses a single-quoted value attribute
with {{ array_value|tojson|safe }}, but the tojson filter doesn't escape
single quotes for HTML attributes. While JSON uses double quotes for
strings, if array_value contains strings with single quotes (like
"Tom's Choice"), the resulting HTML value='["Tom's Choice"]' could
have parsing issues in some browsers when the single quote appears inside
the JSON string content.
The JavaScript equivalent at line 3037 correctly escapes single quotes
with .replace(/'/g, "'"), but the Jinja2 template lacked this
escaping.
Fix by applying the replace filter to escape single quotes:
{{ (array_value|tojson|safe)|replace("'", "'") }}
This ensures consistent behavior between server-side template rendering
and client-side JavaScript rendering, and prevents potential HTML attribute
parsing issues.
* fix: Move hidden input before checkbox for enabled field in custom-feeds
The hidden input and checkbox share the same name, causing duplicate form
values. When request.form.to_dict() processes multiple fields with the same
name, it uses the LAST value.
The previous fix (
|
||
|
|
7f230f625d |
Feature/one shot installer (#175)
* fix(plugins): Remove compatible_versions requirement from single plugin install
Remove compatible_versions from required fields in install_from_url method
to match install_plugin behavior. This allows installing plugins from URLs
without manifest version requirements, consistent with store plugin installation.
* fix(7-segment-clock): Update submodule with separator and spacing fixes
* fix(plugins): Add onchange handlers to existing custom feed inputs
- Add onchange handlers to key and value inputs for existing patternProperties fields
- Fixes bug where editing existing custom RSS feeds didn't save changes
- Ensures hidden JSON input field is updated when users edit feed entries
- Affects all plugins using patternProperties (custom_feeds, feed_logo_map, etc.)
* Add array-of-objects widget support to web UI
- Add support for rendering arrays of objects in web UI (for custom_feeds)
- Implement add/remove/update functions for array-of-objects widgets
- Support file-upload widgets within array items
- Update form data handling to support array JSON data fields
* Update plugins_manager.js cache-busting version
Update version parameter to force browser to load new JavaScript with array-of-objects widget support.
* Fix: Move array-of-objects detection before file-upload/checkbox checks
Move the array-of-objects widget detection to the top of the array handler so it's checked before file-upload and checkbox-group widgets. This ensures custom_feeds is properly detected as an array of objects.
* Update cache-busting version for array-of-objects fix
* Remove duplicate array-of-objects check
* Update cache version again
* Add array-of-objects widget support to server-side template
Add detection and rendering for array-of-objects in the Jinja2 template (plugin_config.html).
This enables the custom_feeds widget to display properly with name, URL, enabled checkbox, and logo upload fields.
The widget is detected by checking if prop.items.type == 'object' && prop.items.properties,
and is rendered before the file-upload widget check.
* Use window. prefix for array-of-objects JavaScript functions
Explicitly use window.addArrayObjectItem, window.removeArrayObjectItem, etc.
in the template to ensure the functions are accessible from inline event handlers.
Also add safety checks to prevent errors if functions aren't loaded yet.
* Fix syntax error: Missing indentation for html += in array else block
The html += statement was outside the else block, causing a syntax error.
Fixed by properly indenting it inside the else block.
* Update cache version for syntax fix
* Add debug logging to diagnose addArrayObjectItem availability
* Fix: Wrap array-of-objects functions in window check and move outside IIFE
Ensure functions are available globally by wrapping them in a window check
and ensuring they're defined outside any IIFE scope. Also fix internal
function calls to use window.updateArrayObjectData for consistency.
* Update cache version for array-of-objects fix
* Move array-of-objects functions outside IIFE to make them globally available
The functions were inside the IIFE scope, making them inaccessible from
inline event handlers. Moving them outside the IIFE ensures they're
available on window when the script loads.
* Update cache version for IIFE fix
* Fix: Add array-of-objects functions after IIFE ends
The functions were removed from inside the IIFE but never added after it.
Also removed orphaned code that was causing syntax errors.
* Update cache version for array-of-objects fix
* Fix: Remove all orphaned code and properly add array-of-objects functions after IIFE
* Add array-of-objects functions after IIFE ends
These functions must be outside the IIFE to be accessible from inline
event handlers in the server-rendered template.
* Update cache version for syntax fix
* Fix syntax error: Add missing closing brace for else block
* Update cache version for syntax fix
* Replace complex array-of-objects widget with simple table interface
- Replace nested array-of-objects widget with clean table interface
- Table shows: Name, URL, Logo (with upload), Enabled checkbox, Delete button
- Fix file-upload widget detection order to prevent breaking static-image plugin
- Add simple JavaScript functions for add/remove rows and logo upload
- Much more intuitive and easier to use
* Add simple table interface for custom feeds
- Replace complex array-of-objects widget with clean table
- Table columns: Name, URL, Logo (upload), Enabled checkbox, Delete
- Use dot notation for form field names (feeds.custom_feeds.0.name)
- Add JavaScript functions for add/remove rows and logo upload
- Fix file-upload detection order to prevent breaking static-image plugin
* Fix custom feeds table issues
- Fix JavaScript error in removeCustomFeedRow (get tbody before removing row)
- Improve array conversion logic to handle nested paths like feeds.custom_feeds
- Add better error handling and debug logging for array conversion
- Ensure dicts with numeric keys are properly converted to arrays before validation
* Add fallback fix for feeds.custom_feeds dict-to-array conversion
- Add explicit fallback conversion for feeds.custom_feeds if fix_array_structures misses it
- This ensures the dict with numeric keys is converted to an array before validation
- Logo field is already optional in schema (not in required array)
* feat(web): Add checkbox-group widget support for plugin config arrays
Add server-side rendering support for checkbox-group widget in plugin
configuration forms. This allows plugins to use checkboxes for multi-select
array fields instead of comma-separated text inputs.
The implementation:
- Checks for x-widget: 'checkbox-group' in schema
- Renders checkboxes for each enum item in items.enum
- Supports custom labels via x-options.labels
- Works with any plugin that follows the pattern
Already used by:
- ledmatrix-news plugin (enabled_feeds)
- odds-ticker plugin (enabled_leagues)
* feat(install): Add one-shot installation script
- Create comprehensive one-shot installer with robust error handling
- Includes network checks, disk space validation, and retry logic
- Handles existing installations gracefully (idempotent)
- Updates README with quick install command prominently featured
- Manual installation instructions moved to collapsible section
The script provides explicit error messages and never fails silently.
All prerequisites are validated before starting installation.
* fix: Remove accidental plugins/7-segment-clock submodule entry
Remove uninitialized submodule 'plugins/7-segment-clock' that was
accidentally included. This submodule is not related to the one-shot
installer feature and should not be part of this PR.
- Remove submodule entry from .gitmodules
- Remove submodule from git index
- Clean up submodule configuration
* fix(array-objects): Fix schema lookup, reindexing, and disable file upload
Address PR review feedback for array-of-objects helpers:
1. Schema resolution: Use getSchemaProperty() instead of manual traversal
- Fixes nested array-of-objects schema lookup (e.g., news.custom_feeds)
- Now properly descends through .properties for nested objects
2. Reindexing: Replace brittle regex with targeted patterns
- Only replace index in bracket notation [0], [1], etc. for names
- Only replace _item_<digits> pattern for IDs (not arbitrary digits)
- Use specific function parameter patterns for onclick handlers
- Prevents corruption of fieldId, pluginId, or other numeric values
3. File upload: Disable widget until properly implemented
- Hide/disable upload button with clear message
- Show existing logos if present but disable upload functionality
- Prevents silent failures when users attempt to upload files
- Added TODO comments for future implementation
Also fixes exit code handling in one-shot-install.sh to properly capture
first_time_install.sh exit status before error trap fires.
* fix(security): Fix XSS vulnerability in handleCustomFeedLogoUpload
Replace innerHTML usage with safe DOM manipulation using createElement
and setAttribute to prevent XSS when injecting uploadedFile.path and
uploadedFile.id values.
- Clear logoCell using textContent instead of innerHTML
- Create all DOM elements using document.createElement
- Set uploadedFile.path and uploadedFile.id via setAttribute (automatically escaped)
- Properly structure DOM tree by appending elements in order
- Prevents malicious HTML/script injection through file path or ID values
* fix: Update upload button onclick when reindexing custom feed rows
Fix removeCustomFeedRow to update button onclick handlers that reference
file input IDs with _logo_<index> when rows are reindexed after deletion.
Previously, after deleting a row, the upload button's onclick still referenced
the old file input ID, causing the upload functionality to fail.
Now properly updates:
- getElementById('..._logo_<num>') patterns in onclick handlers
- Other _logo_<num> patterns in button onclick strings
- Function parameter indices in onclick handlers
This ensures upload buttons continue to work correctly after row deletion.
* fix: Make custom feeds table widget-specific instead of generic fallback
Replace generic array-of-objects check with widget-specific check for
'custom-feeds' widget to prevent hardcoded schema from breaking other
plugins with different array-of-objects structures.
Changes:
- Check for x-widget == 'custom-feeds' before rendering custom feeds table
- Add schema validation to ensure required fields (name, url) exist
- Show warning message if schema doesn't match expected structure
- Fall back to generic array input for other array-of-objects schemas
- Add comments for future generic array-of-objects support
This ensures the hardcoded custom feeds table (name, url, logo, enabled)
only renders when explicitly requested via widget type, preventing
breakage for other plugins with different array-of-objects schemas.
* fix: Add image/gif to custom feed logo upload accept attribute
Update file input accept attributes for custom feed logo uploads to include
image/gif, making it consistent with the file-upload widget which also
allows GIF images.
Updated in three places:
- Template file input (plugin_config.html)
- JavaScript addCustomFeedRow function (base.html)
- Dynamic file input creation in handleCustomFeedLogoUpload (base.html)
All custom feed logo upload inputs now accept: image/png, image/jpeg,
image/bmp, image/gif
* fix: Add hidden input for enabled checkbox to ensure false is submitted
Add hidden input with value='false' before enabled checkbox in custom feeds
table to ensure an explicit false value is sent when checkbox is unchecked.
Pattern implemented:
- Hidden input: name='enabled', value='false' (always submitted)
- Checkbox: name='enabled', value='true' (only submitted when checked)
- When unchecked: only hidden input submits (false)
- When checked: both submit, checkbox value (true) overwrites hidden
Updated in two places:
- Template checkbox in plugin_config.html (existing rows)
- JavaScript addCustomFeedRow function in base.html (new rows)
Backend verification:
- Backend (api_v3.py) handles string boolean values and converts properly
- JavaScript form processing explicitly checks element.checked, independent of this pattern
- Standard form submission uses last value when multiple values share same name
* fix: Expose renderArrayObjectItem to window for addArrayObjectItem
Fix scope issue where renderArrayObjectItem is defined inside IIFE but
window.addArrayObjectItem is defined outside, causing the function check
to always fail and fallback to degraded HTML rendering.
Problem:
- renderArrayObjectItem (line 2469) is inside IIFE (lines 796-6417)
- window.addArrayObjectItem (line 6422) is outside IIFE
- Check 'typeof renderArrayObjectItem === function' at line 6454 always fails
- Fallback code lacks file upload widgets, URL input types, descriptions, styling
Solution:
- Expose renderArrayObjectItem to window object before IIFE closes
- Function maintains closure access to escapeHtml and other IIFE-scoped functions
- Newly added items now have full functionality matching initially rendered items
* fix: Reorder array type checks to match template order
Fix inconsistent rendering where JavaScript and Jinja template had opposite
ordering for array type checks, causing schemas with both x-widget: file-upload
AND items.type: object (like static-image) to render differently.
Problem:
- Template checks file-upload FIRST (to avoid breaking static-image plugin)
- JavaScript checked array-of-objects FIRST
- Server-rendered forms showed file-upload widget correctly
- JS-rendered forms incorrectly displayed array-of-objects table widget
Solution:
- Reorder JavaScript checks to match template order:
1. Check file-upload widget FIRST
2. Check checkbox-group widget
3. Check custom-feeds widget
4. Check array-of-objects as fallback
5. Regular array input (comma-separated)
This ensures consistent rendering between server-rendered and JS-rendered forms
for schemas that have both x-widget: file-upload AND items.type: object.
* fix: Handle None value for feeds config to prevent TypeError
Fix crash when plugin_config['feeds'] exists but is None, causing
TypeError when checking 'custom_feeds' in feeds_config.
Problem:
- When plugin_config['feeds'] exists but is None, dict.get('feeds', {})
returns None (not the default {}) because dict.get() only uses default
when key doesn't exist, not when value is None
- Line 3642's 'custom_feeds' in feeds_config raises TypeError because
None is not iterable
- This can crash the API endpoint if a plugin config has feeds: null
Solution:
- Change plugin_config.get('feeds', {}) to plugin_config.get('feeds') or {}
to ensure feeds_config is always a dict (never None)
- Add feeds_config check before 'in' operator for extra safety
This ensures the code gracefully handles feeds: null in plugin configuration.
* fix: Add default value for AVAILABLE_SPACE to prevent TypeError
Fix crash when df produces unexpected output that results in empty
AVAILABLE_SPACE variable, causing 'integer expression expected' error.
Problem:
- df may produce unexpected output format (different locale, unusual
filesystem name spanning lines, or non-standard df implementation)
- While '|| echo "0"' handles pipeline failures, it doesn't trigger if
awk succeeds but produces no output (empty string)
- When AVAILABLE_SPACE is empty, comparison [ "$AVAILABLE_SPACE" -lt 500 ]
fails with 'integer expression expected' error
- With set -e, this causes script to exit unexpectedly
Solution:
- Add AVAILABLE_SPACE=${AVAILABLE_SPACE:-0} before comparison to ensure
variable always has a numeric value (defaults to 0 if empty)
- This gracefully handles edge cases where df/awk produces unexpected output
* fix: Wrap debug console.log in debug flag check
Fix unconditional debug logging that outputs internal implementation
details to browser console for all users.
Problem:
- console.log('[ARRAY-OBJECTS] Functions defined on window:', ...)
executes unconditionally when page loads
- Outputs debug information about function availability to all users
- Appears to be development/debugging code inadvertently included
- Noisy console output in production
Solution:
- Wrap console.log statement in _PLUGIN_DEBUG_EARLY check to only
output when pluginDebug localStorage flag is enabled
- Matches pattern used elsewhere in the file for debug logging
- Debug info now only visible when explicitly enabled via
localStorage.setItem('pluginDebug', 'true')
* fix: Expose getSchemaProperty, disable upload widget, handle bracket notation arrays
Multiple fixes for array-of-objects and form processing:
1. Expose getSchemaProperty to window (plugins_manager.js):
- getSchemaProperty was defined inside IIFE but needed by global functions
- Added window.getSchemaProperty = getSchemaProperty before IIFE closes
- Updated window.addArrayObjectItem to use window.getSchemaProperty
- Fixes ReferenceError when dynamically adding array items
2. Disable upload widget for custom feeds (plugin_config.html):
- File input and Upload button were still active but should be disabled
- Removed onchange/onclick handlers, added disabled and aria-disabled
- Added visible disabled styling and tooltip
- Existing logos continue to display but uploads are prevented
- Matches PR objectives to disable upload until fully implemented
3. Handle bracket notation array fields (api_v3.py):
- checkbox-group uses name="field_name[]" which sends multiple values
- request.form.to_dict() collapses duplicate keys (only keeps last value)
- Added handling to detect fields ending with "[]" before to_dict()
- Use request.form.getlist() to get all values, combine as comma-separated
- Processed before existing array index field handling
- Fixes checkbox-group losing all but last selected value
* fix: Remove duplicate submit handler to prevent double POSTs
Remove document-level submit listener that conflicts with handlePluginConfigSubmit,
causing duplicate form submissions with divergent payloads.
Problem:
- handlePluginConfigSubmit correctly parses JSON from _data fields and maps to
flatConfig[baseKey] for patternProperties and array-of-objects
- Document-level listener (line 5368) builds its own config without understanding
_data convention and posts independently via savePluginConfiguration
- Every submit now sends two POSTs with divergent payloads:
- First POST: Correct structure with parsed _data fields
- Second POST: Incorrect structure with raw _data fields, missing structure
- Arrays-of-objects and patternProperties saved incorrectly in second request
Solution:
- Remove document-level submit listener for #plugin-config-form
- Rely solely on handlePluginConfigSubmit which is already attached to the form
- handlePluginConfigSubmit properly handles all form-to-config conversion including:
- _data field parsing (JSON from hidden fields)
- Type-aware conversion using schema
- Dot notation to nested object conversion
- PatternProperties and array-of-objects support
Note: savePluginConfiguration function remains for use by JSON editor saves
* fix: Use indexed names for checkbox-group to work with existing parser
Change checkbox-group widget to use indexed field names instead of bracket
notation, so the existing indexed field parser correctly handles multiple
selected values.
Problem:
- checkbox-group uses name="{{ full_key }}[]" which requires bracket
notation handling in backend
- While bracket notation handler exists, using indexed names is more robust
and leverages existing well-tested indexed field parser
- Indexed field parser already handles fields like "field_name.0",
"field_name.1" correctly
Solution:
- Template: Change name="{{ full_key }}[]" to name="{{ full_key }}.{{
loop.index0 }}"
- JavaScript: Update checkbox-group rendering to use name="."
- Backend indexed field parser (lines 3364-3388) already handles this pattern:
- Detects fields ending with numeric indices (e.g., ".0", ".1")
- Groups them by base_path and sorts by index
- Combines into array correctly
This ensures checkbox-group values are properly preserved when multiple
options are selected, working with the existing schema-based parsing system.
* fix: Set values from item data in fallback array-of-objects rendering
Fix fallback code path for rendering array-of-objects items to properly
set input values from existing item data, matching behavior of proper
renderArrayObjectItem function.
Problem:
- Fallback code at lines 3078-3091 and 6471-6486 creates input elements
without setting values from existing item data
- Text inputs have no value attribute set
- Checkboxes have no checked attribute computed from item properties
- Users would see empty form fields instead of existing configuration data
- Proper renderArrayObjectItem function correctly sets values (line 2556)
Solution:
- Extract propValue from item data: item[propKey] with schema default fallback
- For text inputs: Set value attribute with HTML-escaped propValue
- For checkboxes: Set checked attribute based on propValue truthiness
- Add inline HTML escaping for XSS prevention (since fallback code may
run outside IIFE scope where escapeHtml function may not be available)
This ensures fallback rendering displays existing data correctly when
window.renderArrayObjectItem is not available.
* fix: Remove extra closing brace breaking if/else chain
Remove stray closing brace at line 3127 that was breaking the if/else chain
before the 'else if (prop.enum)' branch, causing 'Unexpected token else'
syntax error.
Problem:
- Extra '}' at line 3127 closed the prop.type === 'array' block prematurely
- This broke the if/else chain, causing syntax error when parser reached
'else if (prop.enum)' at line 3128
- Structure was: } else if (array) { ... } } } else if (enum) - extra brace
Solution:
- Removed the extra closing brace at line 3127
- Structure now correctly: } else if (array) { ... } } else if (enum)
- Verified with Node.js syntax checker - no errors
* fix: Remove local logger assignments to prevent UnboundLocalError
Remove all local logger assignments inside save_plugin_config function that
were shadowing the module-level logger, causing UnboundLocalError when nested
helpers like normalize_config_values() or debug checks reference logger before
those assignments run.
Problem:
- Module-level logger exists at line 13: logger = logging.getLogger(__name__)
- Multiple local assignments inside save_plugin_config (lines 3361, 3401, 3421,
3540, 3660, 3977, 4093, 4118) make logger a local variable for entire function
- Python treats logger as local for entire function scope when any assignment
exists, causing UnboundLocalError if logger is used before assignments
- Nested helpers like normalize_config_values() or debug checks that reference
logger before local assignments would fail
Solution:
- Removed all local logger = logging.getLogger(__name__) assignments in
save_plugin_config function
- Use module-level logger directly throughout the function
- Removed redundant import logging statements that were only used for logger
- This ensures logger is always available and references the module-level logger
All logger references now use the module-level logger without shadowing.
* fix: Fix checkbox-group serialization and array-of-objects key leakage
Multiple fixes for array-of-objects and checkbox-group widgets:
1. Fix checkbox-group serialization (JS and template):
- Changed from indexed names (categories.0, categories.1) to _data pattern
- Added updateCheckboxGroupData() function to sync selected values
- Hidden input stores JSON array of selected enum values
- Checkboxes use data-checkbox-group and data-option-value attributes
- Fixes issue where config.categories became {0: true, 1: true} instead of ['nfl', 'nba']
- Now correctly serializes to array using existing _data handling logic
2. Prevent array-of-objects per-item key leakage:
- Added skip pattern in handlePluginConfigSubmit for _item_<n>_ names
- Removed name attributes from per-item inputs in renderArrayObjectItem
- Per-item inputs now rely solely on hidden _data field
- Prevents feeds_item_0_name from leaking into flatConfig
3. Add type coercion to updateArrayObjectData:
- Consults itemsSchema.properties[propKey].type for coercion
- Handles integer and number types correctly
- Preserves string values as-is
- Ensures numeric fields in array items are stored as numbers
4. Ensure currentPluginConfig is always available:
- Updated addArrayObjectItem to check window.currentPluginConfig first
- Added error logging if schema not available
- Prevents ReferenceError when global helpers need schema
This ensures checkbox-group arrays serialize correctly and array-of-objects
per-item fields don't leak extra keys into the configuration.
* fix: Make _data field matching more specific to prevent false positives
Fix overly broad condition that matched any field containing '_data',
causing false positives and inconsistent key transformation.
Problem:
- Condition 'key.endsWith('_data') || key.includes('_data')' matches any
field containing '_data' anywhere (e.g., 'meta_data_field', 'custom_data_config')
- key.replace(/_data$/, '') only removes '_data' from end, making logic inconsistent
- Fields with '_data' in middle get matched but key isn't transformed
- If their value happens to be valid JSON, it gets incorrectly parsed
Solution:
- Remove 'key.includes('_data')' clause
- Only check 'key.endsWith('_data')' to match actual _data suffix pattern
- Ensures consistent matching: only fields ending with '_data' are treated
as JSON data fields, and only those get the suffix removed
- Prevents false positives on fields like 'meta_data_field' that happen to
contain '_data' in their name
* fix: Add HTML escaping to prevent XSS in fallback code and checkbox-group
Add proper HTML escaping for schema-derived values to prevent XSS vulnerabilities
in fallback rendering code and checkbox-group widget.
Problem:
- Fallback code in generateFieldHtml (line 3094) doesn't escape propLabel
when building HTML strings, while main renderArrayObjectItem uses escapeHtml()
- Checkbox-group widget (lines 3012-3025) doesn't escape option or label values
- While risk is limited (values come from plugin schemas), malicious plugin
schemas or untrusted schema sources could inject XSS
- Inconsistent with main renderArrayObjectItem which properly escapes
Solution:
- Added escapeHtml() calls for propLabel in fallback array-of-objects rendering
(both locations: generateFieldHtml and addArrayObjectItem fallback)
- Added escapeHtml() calls for option values in checkbox-group widget:
- checkboxId (contains option)
- data-option-value attribute
- value attribute
- label text in span
- Ensures consistent XSS protection across all rendering paths
This prevents potential XSS if plugin schemas contain malicious HTML/script
content in enum values or property titles.
---------
Co-authored-by: Chuck <chuck@example.com>
|
||
|
|
a13bd971b3 |
fix(plugins): Fix GitHub install and update functionality for plugins installed from URLs (#167)
* fix(plugins): Fix GitHub install button for single plugin installation - Clone install button before attaching event listener to prevent duplicate handlers - Add safety checks for pluginStatusDiv element - Move installFromCustomRegistry function definition earlier in file - Add error logging when button/elements not found - Ensure consistent button reference usage in event handlers Fixes issue where Install button in 'Install Single Plugin' section was not working properly. * fix(plugins): Add button type and better logging for install button - Add type='button' to install button to prevent form submission - Add console logging to debug click handler attachment - Add preventDefault and stopPropagation to click handler - Improve error logging for debugging * fix(plugins): Re-attach install button handler when section is shown - Extract install button handler to separate function - Re-attach handler when GitHub install section is toggled visible - Add data attribute to prevent duplicate handler attachments - Add comprehensive logging for debugging - Handler now attaches even if section starts hidden * fix(plugins): Add comprehensive logging to debug install button handler - Add logging at function entry points - Add logging when section is shown and handler re-attached - Add logging before and after calling attachInstallButtonHandler - Helps diagnose why handler isn't being attached * fix(plugins): Expose GitHub install handlers globally and add fallback - Expose setupGitHubInstallHandlers and attachInstallButtonHandler to window object - Add fallback handler attachment after page load delay - Fix typo in getElementById call - Allows manual testing from browser console - Ensures handlers are accessible even if IIFE scope issues occur * fix(plugins): Add fallback handler attachment after page load * fix(plugins): Ensure GitHub install handlers are set up even if already initialized - Add check to verify setupGitHubInstallHandlers exists before calling - Call setupGitHubInstallHandlers even if initializePlugins was already called - Add comprehensive logging to track function execution - Helps diagnose why handlers aren't being attached * fix(plugins): Add more prominent logging markers for easier debugging * fix(plugins): Add simple standalone handler for GitHub plugin installation - Create handleGitHubPluginInstall() function defined early and globally - Add inline onclick handler to button as fallback - Bypasses complex initialization flow and IIFE scope issues - Direct approach that works immediately without dependencies - Provides clear error messages and logging * chore: Update 7-segment-clock plugin submodule - Update to latest version with scaling support - Includes compatible_versions field fix for plugin store installation * fix(plugins): Add update and uninstall handling to global event delegation fallback - Add 'update' action handling in handleGlobalPluginAction fallback - Add 'uninstall' action handling with confirmation dialog - Fixes issue where update/uninstall buttons did nothing - Buttons now work even if handlePluginAction isn't available yet * fix(plugins): Improve error message for plugin updates from GitHub URLs - Check if plugin is a git repository before checking registry - Provide more accurate error messages for plugins installed from URLs - Fixes misleading 'Plugin not found in registry' error for git-based plugins - Update should work for plugins installed from GitHub URLs even if not in registry * fix(plugins): Add detailed logging for plugin update failures - Log git command that failed and return code - Add logging before/after update attempt - Log whether plugin is detected as git repository - Helps diagnose why updates fail for plugins installed from URLs * fix(plugins): Add better logging for plugin update detection - Log when plugin is detected as git repository - Log when plugin is not a git repository - Provide helpful message for ZIP-installed plugins - Helps diagnose why updates fail for plugins installed from URLs * fix(plugins): Enable updates for plugins installed from GitHub URLs - Get git remote URL from plugin directory even if .git is missing - If plugin not in registry but has remote URL, reinstall as git repo - Allows updating plugins installed from URLs even if git clone failed initially - Falls back to reinstalling from original URL to enable future updates * fix(plugins): Reinstall from git remote URL if plugin not in registry - When plugin is not a git repo and not in registry, check for git remote URL - If remote URL exists, reinstall plugin from that URL to enable future updates - Handles case where plugin was installed from URL but git clone failed initially * fix(plugins): Improve git update error handling and logging - Make git fetch non-fatal (log warning but continue) - Make git checkout non-fatal (log warning but continue) - Add detailed error messages for common git failures - Log which git command failed and return code - Better handling of authentication, merge conflicts, and unrelated histories * fix(plugins): Add detailed exception logging to update endpoint - Log full traceback when update fails - Log exception details in catch block - Helps diagnose update failures from API endpoint * fix(plugins): Handle untracked files during plugin update - Remove .dependencies_installed marker file before pull (safe to regenerate) - Stash untracked files using 'git stash -u' if they can't be removed - Prevents 'untracked files would be overwritten' errors during update - Fixes issue where .dependencies_installed blocks git pull * chore: Update 7-segment-clock submodule with improved clone instructions --------- Co-authored-by: Chuck <chuck@example.com> |
||
|
|
67197635c9 |
Feature/on demand plugin filtering (#166)
* fix(web): Resolve font display and config API error handling issues
- Fix font catalog display error where path.startsWith fails
(path is object, not string)
- Update save_main_config to use error_response() helper
- Improve save_raw_main_config error handling consistency
- Add proper error codes and traceback details to API responses
* fix(web): Prevent fontCatalog redeclaration error on HTMX reload
- Use window object to store global font variables
- Check if script has already loaded before declaring variables
- Update both window properties and local references on assignment
- Fixes 'Identifier fontCatalog has already been declared' error
* fix(web): Wrap fonts script in IIFE to prevent all redeclaration errors
- Wrap entire script in IIFE that only runs once
- Check if script already loaded before declaring variables/functions
- Expose initializeFontsTab to window for re-initialization
- Prevents 'Identifier has already been declared' errors on HTMX reload
* fix(web): Exempt config save API endpoints from CSRF protection
- Exempt save_raw_main_config, save_raw_secrets_config, and save_main_config from CSRF
- These endpoints are called via fetch from JavaScript and don't include CSRF tokens
- Fixes 500 error when saving config via raw JSON editor
* fix(web): Exempt system action endpoint from CSRF protection
- Exempt execute_system_action from CSRF
- Fixes 500 error when using system action buttons (restart display, restart Pi, etc.)
- These endpoints are called via HTMX and don't include CSRF tokens
* fix(web): Exempt all API v3 endpoints from CSRF protection
- Add before_request handler to exempt all api_v3.* endpoints
- All API endpoints are programmatic (HTMX/fetch) and don't include CSRF tokens
- Prevents future CSRF errors on any API endpoint
- Cleaner than exempting individual endpoints
* refactor(web): Remove CSRF protection for local-only application
- CSRF is designed for internet-facing apps to prevent cross-site attacks
- For local-only Raspberry Pi app, threat model is different
- All endpoints were exempted anyway, so it wasn't protecting anything
- Forms use HTMX without CSRF tokens
- If exposing to internet later, can re-enable with proper token implementation
* fix(web): Fix font path double-prefixing in font catalog display
- Only prefix with 'assets/fonts/' if path is a bare filename
- If path starts with '/' (absolute) or 'assets/' (already prefixed), use as-is
- Fixes double-prefixing when get_fonts_catalog returns relative paths like 'assets/fonts/press_start.ttf'
* fix(web): Remove fontsTabInitialized guard to allow re-initialization on HTMX reload
- Remove fontsTabInitialized check that prevented re-initialization on HTMX content swap
- The window._fontsScriptLoaded guard is sufficient to prevent function redeclaration
- Allow initializeFontsTab() to run on each HTMX swap to attach listeners to new DOM elements
- Fixes fonts UI breaking after HTMX reload (buttons, upload dropzone, etc. not working)
* fix(api): Preserve empty strings for optional string fields in plugin config
- Add _is_field_required() helper to check if fields are required in schema
- Update _parse_form_value_with_schema() to preserve empty strings for optional string fields
- Fixes 400 error when saving MQTT plugin config with empty username/password
- Resolves validation error: 'Expected type string, got NoneType'
* fix(config): Add defaults to schemas and fix None value handling
- Updated merge_with_defaults to replace None values with defaults
- Fixed form processing to skip empty optional fields without defaults
- Added script to automatically add defaults to all plugin config schemas
- Added defaults to 89 fields across 10 plugin schemas
- Prevents validation errors from None values in configs
Changes:
- schema_manager.py: Enhanced merge_with_defaults to replace None with defaults
- api_v3.py: Added _SKIP_FIELD sentinel to skip optional fields without defaults
- add_defaults_to_schemas.py: Script to add sensible defaults to schemas
- Plugin schemas: Added defaults for number, boolean, and array fields
* fix(config): Fix save button spinner by checking HTTP status code
- Fixed handleConfigSave to check xhr.status instead of event.detail.successful
- With hx-swap="none", HTMX doesn't set event.detail.successful
- Now properly detects successful saves (status 200-299) and stops spinner
- Improved error message extraction from API responses
- Also fixed handleToggleResponse for consistency
* fix(web-ui): Resolve GitHub token warning persistence after save
- Made checkGitHubAuthStatus() return Promise for proper async handling
- Clear sessionStorage dismissal flag when token is saved
- Add delay before status check to ensure backend token reload
- Wait for status check completion before hiding settings panel
Fixes issue where GitHub token warnings and pop-ups would not
disappear after successfully saving a token in the web UI.
* fix(web-ui): Add token validation and improve GitHub token warning behavior
- Add token validation to backend API endpoint to check if token is valid/expired
- Implement _validate_github_token() method in PluginStoreManager with caching
- Update frontend to show warning only when token is missing or invalid
- Keep settings panel accessible (collapsible) when token is configured
- Collapse settings panel content after successful token save instead of hiding
- Display specific error messages for invalid/expired tokens
- Clear sessionStorage dismissal flag when token becomes valid
Fixes issue where GitHub token warnings and settings panel would not
properly hide/show based on token status. Now validates token validity
and provides better UX with collapsible settings panel.
* fix(web-ui): Fix CSS/display issue for GitHub token warning and settings
- Update all hide/show operations to use both classList and style.display
- Fix checkGitHubAuthStatus() to properly hide/show warning and settings
- Fix dismissGithubWarning() to use both methods
- Fix toggleGithubTokenSettings() with improved state checking
- Fix collapse button handler with improved state checking
- Fix saveGithubToken() to properly show/collapse settings panel
This ensures elements actually hide/show when status changes, matching
the pattern used elsewhere in the codebase (like toggleSection). All
buttons (dismiss, close, collapse) should now work correctly.
* fix(web-ui): Fix GitHub token expand button functionality
- Convert collapse button handler to named function (toggleGithubTokenContent)
- Improve state checking using class, inline style, and computed style
- Re-attach event listener after saving token to ensure it works
- Add console logging for debugging
- Make function globally accessible for better reliability
Fixes issue where expand button didn't work after saving token.
* fix(web-ui): Remove X button and improve GitHub token panel behavior
- Remove X (close) button from GitHub token configuration panel
- Replace toggleGithubTokenSettings() with openGithubTokenSettings() that only opens
- Auto-collapse panel when token is valid (user must click expand to edit)
- Auto-detect token status on page load (no need to click save)
- Simplify saveGithubToken() to rely on checkGitHubAuthStatus() for UI updates
- Ensure expand button works correctly with proper event listener attachment
The panel now remains visible but collapsed when a token is configured,
allowing users to expand it when needed without the ability to completely hide it.
* refactor(web-ui): Improve GitHub token collapse button code quality
- Update comment to reflect actual behavior (prevent parent click handlers)
- Use empty string for display to defer to CSS instead of hard-coding block/none
- Extract duplicate clone-and-attach logic into attachGithubTokenCollapseHandler() helper
- Make helper function globally accessible for reuse in checkGitHubAuthStatus()
Improves maintainability and makes code more future-proof for layout changes.
* fix(web-ui): Fix collapse/expand button by using removeProperty for display
- Use style.removeProperty('display') instead of style.display = ''
- This properly removes inline styles and defers to CSS classes
- Fixes issue where collapse/expand button stopped working after refactor
* fix(web-ui): Make display handling consistent for token collapse
- Use removeProperty('display') consistently in all places
- Fix checkGitHubAuthStatus() to use removeProperty instead of inline style
- Simplify state checking to rely on hidden class with computed style fallback
- Ensures collapse/expand button works correctly by deferring to CSS classes
* fix(web-ui): Fix token collapse button and simplify state detection
- Simplify state checking to rely on hidden class only (element has class='block')
- Only remove inline display style if it exists (check before removing)
- Add console logging to debug handler attachment
- Ensure collapse/expand works by relying on CSS classes
Fixes issues where:
- Collapse button did nothing
- Auto-detection of token status wasn't working
* debug(web-ui): Add extensive debugging for token collapse button
- Add console logs to track function calls and element detection
- Improve state detection to use computed style as fallback
- Add wrapper function for click handler to ensure it's called
- Better error messages to identify why handler might not attach
This will help identify why the collapse button isn't working.
* debug(web-ui): Add comprehensive debugging for GitHub token features
- Add console logs to checkGitHubAuthStatus() to track execution
- Re-attach collapse handler after plugin store is rendered
- Add error stack traces for better debugging
- Ensure handler is attached when content is dynamically loaded
This will help identify why:
- Auto-detection of token status isn't working
- Collapse button isn't functioning
* fix(web-ui): Move checkGitHubAuthStatus before IIFE to fix scope issue
- Move checkGitHubAuthStatus function definition before IIFE starts
- Function was defined after IIFE but called inside it, causing it to be undefined
- Now function is available when called during initialization
- This should fix auto-detection of token status on page load
* debug(web-ui): Add extensive logging to GitHub token functions
- Add logging when checkGitHubAuthStatus is defined
- Add logging when function is called during initialization
- Add logging in attachGithubTokenCollapseHandler
- Add logging in store render callback
- This will help identify why functions aren't executing
* fix(web-ui): Move GitHub token functions outside IIFE for availability
- Move attachGithubTokenCollapseHandler and toggleGithubTokenContent outside IIFE
- These functions need to be available when store renders, before IIFE completes
- Add logging to initializePlugins to track when it's called
- This should fix the 'undefined' error when store tries to attach handlers
* fix(web-ui): Fix GitHub token content collapse/expand functionality
- Element has 'block' class in HTML which conflicts with 'hidden' class
- When hiding: add 'hidden', remove 'block', set display:none inline
- When showing: remove 'hidden', add 'block', remove inline display
- This ensures proper visibility toggle for the GitHub API Configuration section
* feat(display): Implement on-demand plugin filtering with restart
- Add on-demand plugin filtering to DisplayController initialization
- Filters available_modes to only include on-demand plugin's modes
- Allows plugin internal rotation (e.g., NFL upcoming, NCAA FB Recent)
- Prevents rotation to other plugins
- Implement restart mechanism for on-demand activation/clear
- _restart_with_on_demand_filter() saves state and restarts with filter
- _restart_without_on_demand_filter() restores normal operation
- Supports both systemd service and direct process execution
- Add state preservation across restarts
- Saves/restores rotation position from cache
- Restores on-demand config from cache after restart
- Add service detection method
- Detects if running as systemd service
- Uses file-based approach for environment variable passing
- Update API endpoints with restart flow comments
- Update systemd service file with on-demand support notes
- Add comprehensive error handling for edge cases
* perf(web-ui): Optimize GitHub token detection speed
- Call checkGitHubAuthStatus immediately when script loads (if elements exist)
- Call it early in initPluginsPage (before full initialization completes)
- Use requestAnimationFrame instead of setTimeout(100ms) for store render callback
- Reduce save token delay from 300ms to 100ms
- Token detection now happens in parallel with other initialization tasks
- This makes token status visible much faster on page load
* fix(ui): Move on-demand modal to base.html for always-available access
- Move on-demand modal from plugins.html to base.html
- Ensures modal is always in DOM when Run On-Demand button is clicked
- Fixes issue where button in plugin_config.html couldn't find modal
- Modal is now available regardless of which tab is active
* fix(ui): Initialize on-demand modal unconditionally on page load
- Create initializeOnDemandModal() function that runs regardless of plugins tab
- Modal is in base.html so it should always be available
- Call initialization on DOMContentLoaded and with timeout
- Fixes 'On-demand modal elements not found' error when clicking button
- Modal setup now happens even if plugins tab hasn't been loaded yet
* fix(ui): Add safety check for updatePluginTabStates function
- Check if updatePluginTabStates exists before calling
- Prevents TypeError when function is not available
- Fixes error when clicking plugin tabs
* fix(ui): Add safety checks for all updatePluginTabStates calls
- Add safety check in Alpine component tab button handler
- Add safety check in Alpine callback
- Prevents TypeError when function is not available in all contexts
* fix(ui): Add safety check in Alpine callback for updatePluginTabStates
* debug(ui): Add console logging to trace on-demand modal opening
- Add logging to runPluginOnDemand function
- Add logging to __openOnDemandModalImpl function
- Log plugin lookup, modal element checks, and display changes
- Helps diagnose why modal doesn't open when button is clicked
* debug(ui): Add logging for modal display change
* debug(ui): Add more explicit modal visibility settings and computed style logging
- Set visibility and opacity explicitly when showing modal
- Force reflow to ensure styles are applied
- Log computed styles to diagnose CSS issues
- Helps identify if modal is hidden by CSS rules
* debug(ui): Increase modal z-index and add bounding rect check
- Set z-index to 9999 to ensure modal is above all other elements
- Add bounding rect check to verify modal is in viewport
- Helps diagnose if modal is positioned off-screen or behind other elements
* debug(display): Add detailed logging for on-demand restart flow
- Log when polling finds requests
- Log service detection result
- Log file writing and systemctl commands
- Log restart command execution and results
- Helps diagnose why on-demand restart isn't working
* debug(display): Add logging for on-demand request polling
- Log request_id comparison to diagnose why requests aren't being processed
- Helps identify if request_id matching is preventing processing
* fix(ui): Force modal positioning with !important to override any conflicting styles
- Use cssText with !important flags to ensure modal is always visible
- Remove all inline styles first to start fresh
- Ensure modal is positioned at top:0, left:0 with fixed positioning
- Fixes issue where modal was still positioned off-screen (top: 2422px)
* debug(ui): Add logging to on-demand form submission
- Log form submission events
- Log payload being sent
- Log response status and data
- Helps diagnose why on-demand requests aren't being processed
* fix(display): Remove restart-based on-demand activation
- Replace restart-based activation with immediate mode switch
- On-demand now activates without restarting the service
- Saves rotation state for restoration when on-demand ends
- Fixes infinite restart loop issue
- On-demand now works when display is already running
* docs: Add comprehensive guide for on-demand cache management
- Document all on-demand cache keys and their purposes
- Explain when manual clearing is needed
- Clarify what clearing from cache management tab does/doesn't do
- Provide troubleshooting steps and best practices
* fix(display): Ensure on-demand takes priority over live priority
- Move on-demand check BEFORE live priority check
- Add explicit logging when on-demand overrides live priority
- Improve request_id checking with both instance and persisted checks
- Add debug logging to trace why requests aren't being processed
- Fixes issue where on-demand didn't interrupt live NHL game
* fix(display): Ensure on-demand takes priority over live priority
- Move on-demand check BEFORE live priority check in main loop
- Add explicit logging when on-demand overrides live priority
- Fixes issue where on-demand didn't interrupt live NHL game
* fix(display): Improve on-demand request processing and priority
- Add persistent processed_id check to prevent duplicate processing
- Mark request as processed BEFORE processing to prevent race conditions
- Improve logging to trace request processing
- Ensure on-demand takes priority over live priority (already fixed in previous commit)
* fix(display): Remove duplicate action line
* fix(display): Fix live priority and ensure on-demand overrides it
- Fix live priority to properly set active_mode when live content is detected
- Ensure on-demand check happens before live priority check
- Add debug logging to trace on-demand vs live priority
- Fix live priority to stay on live mode instead of rotating
* fix(display): Add debug logging for on-demand priority check
* fix(display): Add better logging for on-demand request processing
- Add logging to show when requests are blocked by processed_id check
- Add logging to show on-demand state after activation
- Helps debug why on-demand requests aren't being processed
* fix(display): Add detailed logging for on-demand activation and checking
- Log on-demand state after activation to verify it's set correctly
- Add debug logging in main loop to trace on-demand check
- Helps identify why on-demand isn't overriding live priority
* fix(display): Add debug logging for on-demand check in main loop
* fix(display): Remove restart logic from _clear_on_demand and fix cache delete
- Replace cache_manager.delete() with cache_manager.clear_cache()
- Remove restart logic from _clear_on_demand - now clears immediately
- Restore rotation state immediately without restarting
- Fixes AttributeError: 'CacheManager' object has no attribute 'delete'
* fix(display): Remove restart logic from _clear_on_demand
- Remove restart logic - now clears on-demand state immediately
- Restore rotation state immediately without restarting
- Use clear_cache instead of delete (already fixed in previous commit)
- Fixes error when stopping on-demand mode
* feat(display): Clear display before activating on-demand mode
- Clear display and reset state before activating on-demand
- Reset dynamic mode state to ensure clean transition
- Mimics the behavior of manually stopping display first
- Should fix issue where on-demand only works after manual stop
* feat(display): Stop display service before starting on-demand mode
- Stop the display service first if it's running
- Wait 1.5 seconds for clean shutdown
- Then start the service with on-demand request in cache
- Mimics the manual workflow of stopping display first
- Should fix issue where on-demand only works after manual stop
* feat(display): Filter plugins during initialization for on-demand mode
- Check cache for on-demand requests during initialization
- Only load the on-demand plugin if on-demand request is found
- Prevents loading background services for other plugins
- Fixes issue where Hockey/Football data loads even when only Clock is requested
* fix(display): Use filtered enabled_plugins list instead of discovered_plugins
- Use enabled_plugins list which is already filtered for on-demand mode
- Prevents loading all plugins when on-demand mode is active
- Fixes issue where all plugins were loaded even in on-demand mode
* fix(display): Fix on-demand stop request processing and expiration check
- Always process stop requests, even if request_id was seen before
- Fix expiration check to handle cases where on-demand is not active
- Add better logging for stop requests and expiration
- Fixes issue where stop button does nothing and timer doesn't expire
* fix(display): Fix on-demand stop processing, expiration, and plugin filtering
- Fix stop request processing to always process stop requests, bypassing request_id checks
- Fix expiration check logic to properly check on_demand_active and expires_at separately
- Store display_on_demand_config cache key in _activate_on_demand for plugin filtering
- Clear display before switching to on-demand mode to prevent visual artifacts
- Clear display_on_demand_config cache key in _clear_on_demand to prevent stale data
- Implement plugin filtering during initialization based on display_on_demand_config
Fixes issues where:
- Stop button did nothing (stop requests were blocked by request_id check)
- Expiration timer didn't work (logic issue with or condition)
- Plugin filtering didn't work on restart (config cache key never set)
- Display showed artifacts when switching to on-demand (display not cleared)
- All plugins loaded even in on-demand mode (filtering not implemented)
* fix(web): Allow on-demand to work with disabled plugins
- Remove frontend checks that blocked disabled plugins from on-demand
- Backend already supports temporarily enabling disabled plugins during on-demand
- Update UI messages to indicate plugin will be temporarily enabled
- Remove disabled attribute from Run On-Demand button
Fixes issue where disabled plugins couldn't use on-demand feature even
though the backend implementation supports it.
* fix(display): Resolve plugin_id when sent as mode in on-demand requests
- Detect when mode parameter is actually a plugin_id and resolve to first display mode
- Handle case where frontend sends plugin_id as mode (e.g., 'football-scoreboard')
- Add fallback to use first available display mode if provided mode is invalid
- Add logging for mode resolution debugging
Fixes issue where on-demand requests with mode=plugin_id failed with 'invalid-mode' error
* feat(display): Rotate through all plugin modes in on-demand mode
- Store all modes for on-demand plugin instead of locking to single mode
- Rotate through available modes (live, recent, upcoming) when on-demand active
- Skip modes that return False (no content) and move to next mode
- Prioritize live modes if they have content, otherwise skip them
- Add on_demand_modes list and on_demand_mode_index for rotation tracking
Fixes issue where on-demand mode stayed on one mode (e.g., football_recent)
and didn't rotate through other available modes (football_live, football_upcoming).
Now properly rotates through all modes, skipping empty ones.
* fix(display): Improve on-demand stop request handling
- Always process stop requests if on-demand is active, even if same request_id
- Add better logging when stop is requested but on-demand is not active
- Improve logging in _clear_on_demand to show which mode rotation resumes to
- Ensure stop requests are properly acknowledged
Fixes issue where stop button shows as completed but display doesn't resume
normal rotation. Stop requests now properly clear on-demand state and resume.
* security(web): Fix XSS vulnerability in GitHub auth error display
Replace innerHTML usage with safe DOM manipulation:
- Use textContent to clear element and create text nodes
- Create <strong> element via createElement instead of string HTML
- Add safe fallback ('Unknown error') for error messages
- Ensure authData.error/authData.message are treated as plain text
- Avoid trusting backend-provided data as HTML
Fixes XSS vulnerability where malicious HTML in error messages could
be injected into the DOM.
* style(api): Remove unnecessary str() in f-string for error message
Remove explicit str(e) call in error_response f-string since f-strings
automatically convert exceptions to strings. This matches the style used
elsewhere in the file.
Changed: f"Error saving configuration: {str(e)}"
To: f"Error saving configuration: {e}"
* fix(store): Skip caching for rate-limited 403 responses
When a 403 response indicates a rate limit (detected by checking if
'rate limit' is in response.text.lower()), return the error result but
do NOT cache it in _token_validation_cache. Rate limits are temporary
and should be retried, so caching would incorrectly mark the token as
invalid.
Continue to cache 403 responses that indicate missing token permissions,
as these are persistent issues that should be cached.
This prevents rate-limited responses from being incorrectly cached as
invalid tokens, allowing the system to retry after the rate limit
resets.
* fix(display): Prevent ZeroDivisionError when on_demand_modes is empty
Add guards to check if on_demand_modes is non-empty before performing
any rotation/index math operations. When on_demand_active is True but
on_demand_modes is empty, clear on-demand mode instead of attempting
division by zero.
Fixed in three locations:
1. Mode selection logic (line ~1081): Check before accessing modes
2. Skip to next mode when no content (line ~1190): Guard before modulo
3. Rotate to next mode (line ~1561): Guard before modulo
This prevents ZeroDivisionError when a plugin has no available display
modes or when on_demand_modes becomes empty unexpectedly.
* fix(display): Improve guard for empty on_demand_modes in rotation skip
Refine the guard around lines 1195-1209 to:
- Check if on_demand_modes is empty before any modulo/index operations
- Log warning and debug trace when no modes are configured
- Skip rotation (continue) instead of clearing on-demand mode
- Only perform modulo and index operations when modes are available
- Only log rotation message when next_mode is valid
This prevents ZeroDivisionError and ensures all logging only occurs
when next_mode is valid, providing better traceability.
* fix(display): Populate on_demand_modes when restoring on-demand state from cache
When restoring on-demand state from cache during initialization (around
lines 163-197), the code sets on_demand_active, on_demand_plugin_id and
related fields but does not populate self.on_demand_modes, causing the
run loop to see an empty modes list after restart.
Fix by:
1. Adding _populate_on_demand_modes_from_plugin() method that retrieves
the plugin's display modes from plugin_display_modes and builds the
ordered modes list (prioritizing live modes with content, same logic
as _activate_on_demand)
2. Calling this method after plugin loading completes (around line 296)
when on_demand_active and on_demand_plugin_id are set
3. Setting on_demand_mode_index to match the restored mode if available,
otherwise starting at index 0
This ensures on_demand_modes is populated after restart, preventing
empty modes list errors in the run loop.
* docs: Update on-demand documentation to reflect current implementation
Replace obsolete log message reference with current log messages:
- Old: 'Activating on-demand mode... restarting display controller'
- New: 'Processing on-demand start request for plugin' and 'Activated on-demand for plugin'
Update Scenario 2 to reflect immediate mode switching:
- Changed title from 'Infinite Restart Loop' to 'On-Demand Mode Switching Issues'
- Updated symptoms to describe mode switching issues instead of restart loops
- Added note that on-demand now switches modes immediately without restarting
- Updated solution to include display_on_demand_state key
This reflects the current implementation where on-demand activates
immediately without restarting the service.
* fix(api): Fix undefined logger and service stop logic in start_on_demand_display
- Add module-level logger to avoid NameError when logging disabled plugin
- Only stop display service when start_service is True (prevents stopping
service without restarting when start_service is False)
- Remove unused stop_result variable
- Clean up f-strings that don't need formatting
- Improve code formatting for logger.info call
Fixes issue where logger.info() would raise NameError and where the
service would be stopped even when start_service=False, leaving the
service stopped without restarting it.
---------
Signed-off-by: Chuck <33324927+ChuckBuilds@users.noreply.github.com>
Co-authored-by: Chuck <chuck@example.com>
|
||
|
|
a5c10d6f78 |
fix(web-ui): Fix file upload widget and plugin action buttons (#165)
* fix(plugins): Resolve plugin ID determination error in action buttons
- Fix server-side template parameter order for executePluginAction
- Add data-plugin-id attributes to action buttons in all templates
- Enhance executePluginAction with comprehensive fallback logic
- Support retrieving pluginId from DOM, Alpine context, and config state
- Fixes 'Unable to determine plugin ID' error for Spotify/YouTube auth
* fix(plugins): Add missing button IDs and status divs in server-side action template
- Add action-{id}-{index} IDs to action buttons
- Add action-status-{id}-{index} status divs for each action
- Match client-side template structure for consistency
- Fixes 'Action elements not found' error
* fix(api): Fix indentation error in execute_plugin_action function
- Fix incorrect else block indentation that caused 500 errors
- Correct indentation for OAuth flow and simple script execution paths
- Resolves syntax error preventing plugin actions from executing
* fix(api): Improve error handling for plugin actions and config saves
- Add better JSON parsing error handling with request details
- Add detailed permission error messages for secrets file saves
- Include file path and permission status in error responses
- Helps diagnose 400 errors on action execution and 500 errors on config saves
* fix(api): Add detailed permission error handling for secrets config saves
- Add PermissionError-specific handling with permission checks
- Include directory and file permission status in error logs
- Provide more helpful error messages with file paths
- Helps diagnose permission issues when saving config_secrets.json
* fix(config): Add permission check and actionable error message for config saves
- Check file writability before attempting write
- Show file owner and current permissions in error message
- Provide exact command to fix permissions (chown + chmod)
- Helps diagnose and resolve permission issues with config_secrets.json
* fix(config): Preserve detailed permission error messages
- Handle PermissionError separately to preserve detailed error messages
- Ensure actionable permission fix commands are included in error response
- Prevents detailed error messages from being lost in exception chain
* fix(config): Remove overly strict pre-write permission check
- Remove pre-write file existence/writability check that was blocking valid writes
- Let actual file write operation determine success/failure
- Provide detailed error messages only when write actually fails
- Fixes regression where config_secrets.json saves were blocked unnecessarily
* fix(config): Use atomic writes for config_secrets.json to handle permission issues
- Write to temp file first, then atomically move to final location
- Works even when existing file isn't writable (as long as directory is writable)
- Matches pattern used elsewhere in codebase (disk_cache, atomic_manager)
- Fixes permission errors when saving secrets configuration
* chore: Update music plugin submodule to include live_priority fix
* fix(plugins): Improve plugin ID determination in dynamic button generation
- Update generateFormFromSchema to pass currentPluginConfig?.pluginId and add data attributes
- Update generateSimpleConfigForm to pass currentPluginConfig?.pluginId and add data attributes
- Scope fallback 6 DOM lookup to button context instead of document-wide search
- Ensures correct plugin tab selection when multiple plugins are present
- Maintains existing try/catch error handling and logging
* chore: Update music plugin submodule to fix has_live_priority enabled attribute
* chore: Update music plugin submodule - remove redundant music_priority_mode
* fix(web-ui): Fix file upload widget detection for nested plugin properties
- Added helper function to get schema properties by full key path
- Enhanced x-widget detection to check both property object and schema directly
- Improved upload config retrieval with fallback to schema
- Added debug logging for file-upload widget detection
- Fixes issue where static-image plugin file upload widget was not rendering
The file upload widget was not being detected for nested properties like
image_config.images because the x-widget attribute wasn't being checked
in the schema directly. This fix ensures the widget is properly detected
and rendered even when nested deep in the configuration structure.
* fix(web-ui): Improve file upload widget detection with direct schema fallback
- Fixed getSchemaProperty helper function to correctly navigate nested paths
- Added direct schema lookup fallback for image_config.images path
- Enhanced debug logging to diagnose widget detection issues
- Simplified widget detection logic while maintaining robustness
* fix(web-ui): Add aggressive schema lookup for file-upload widget detection
- Always try direct schema navigation for image_config.images
- Added general direct lookup fallback if getSchemaProperty fails
- Enhanced debug logging with schema existence checks
- Prioritize schema lookup over prop object for x-widget detection
* fix(web-ui): Add direct check for top-level images field in file upload detection
- Added specific check for top-level 'images' field (flattened schema)
- Enhanced debug logging to show all x-widget detection attempts
- Improved widget detection to check prop object more thoroughly
* fix(web-ui): Prioritize prop object for x-widget detection
- Check prop object first (should have x-widget from schema)
- Then fall back to schema lookup
- Enhanced debug logging to show all detection attempts
* fix(web-ui): Add aggressive direct detection for images file upload widget
- Added direct check for 'images' field in schema.properties.images
- Multiple fallback detection methods (direct, prop object, schema lookup)
- Simplified logic to explicitly check for file-upload widget
- Enhanced debug logging to show detection path
* fix(web-ui): Add file upload widget support to server-side Jinja2 template
- Added check for x-widget: file-upload in array field rendering
- Renders file upload drop zone with drag-and-drop support
- Displays uploaded images list with delete and schedule buttons
- Falls back to comma-separated text input for regular arrays
- Fixes file upload widget not appearing in static-image plugin
* feat(web-ui): Add route to serve plugin asset files from assets directory
- Added /assets/plugins/<plugin_id>/uploads/<filename> route
- Serves uploaded images and other assets with proper content types
- Includes security checks to prevent directory traversal
- Fixes 404 errors when displaying uploaded plugin images
* fix(web-ui): Fix import for send_from_directory in plugin assets route
* feat(web-ui): Load uploaded images from metadata file when rendering config form
- Populates images field from .metadata.json if not in config
- Ensures uploaded images appear in form even before config is saved
- Merges metadata images with existing config images to avoid duplicates
* fix(web-ui): Fix PROJECT_ROOT reference in image metadata loading
* docs(web-ui): Add reminder to save configuration after file upload
- Added informational note below upload widget
- Reminds users to save config after uploading files
- Uses amber color and info icon for visibility
* fix(web-ui): Move plugin asset serving route to main app
- Moved /assets/plugins/... route from api_v3 blueprint to main app
- Blueprint has /api/v3 prefix, but route needs to be at /assets/...
- Fixes 404 errors when trying to display uploaded images
- Route must be on main app for correct URL path
* security(web-ui): Fix path containment check in plugin asset serving
- Replace string startswith() with proper path resolution using os.path.commonpath()
- Prevents prefix-based directory traversal bypasses
- Uses resolved absolute paths to ensure true path containment
- Handles ValueError for cross-drive paths (Windows compatibility)
* security(web-ui): Remove traceback exposure from plugin asset serving errors
- Return generic error message instead of full traceback in production
- Log exceptions server-side using app.logger.exception()
- Only include detailed error information when app.debug is True
- Prevents leaking internal implementation details to clients
* fix(web-ui): Assign currentPluginConfig to window for template access
- Assign currentPluginConfig to window.currentPluginConfig when building the object
- Fixes empty pluginId in template interpolation for plugin action buttons
- Ensures window.currentPluginConfig?.pluginId is available in onclick handlers
- Prevents executePluginAction from receiving empty pluginId parameter
* chore: Update music plugin submodule to include .gitignore
---------
Co-authored-by: Chuck <chuck@example.com>
|
||
|
|
24c34c5a40 |
fix(plugins): Resolve plugin action button errors and config save permission issues (#162)
* fix(plugins): Resolve plugin ID determination error in action buttons
- Fix server-side template parameter order for executePluginAction
- Add data-plugin-id attributes to action buttons in all templates
- Enhance executePluginAction with comprehensive fallback logic
- Support retrieving pluginId from DOM, Alpine context, and config state
- Fixes 'Unable to determine plugin ID' error for Spotify/YouTube auth
* fix(plugins): Add missing button IDs and status divs in server-side action template
- Add action-{id}-{index} IDs to action buttons
- Add action-status-{id}-{index} status divs for each action
- Match client-side template structure for consistency
- Fixes 'Action elements not found' error
* fix(api): Fix indentation error in execute_plugin_action function
- Fix incorrect else block indentation that caused 500 errors
- Correct indentation for OAuth flow and simple script execution paths
- Resolves syntax error preventing plugin actions from executing
* fix(api): Improve error handling for plugin actions and config saves
- Add better JSON parsing error handling with request details
- Add detailed permission error messages for secrets file saves
- Include file path and permission status in error responses
- Helps diagnose 400 errors on action execution and 500 errors on config saves
* fix(api): Add detailed permission error handling for secrets config saves
- Add PermissionError-specific handling with permission checks
- Include directory and file permission status in error logs
- Provide more helpful error messages with file paths
- Helps diagnose permission issues when saving config_secrets.json
* fix(config): Add permission check and actionable error message for config saves
- Check file writability before attempting write
- Show file owner and current permissions in error message
- Provide exact command to fix permissions (chown + chmod)
- Helps diagnose and resolve permission issues with config_secrets.json
* fix(config): Preserve detailed permission error messages
- Handle PermissionError separately to preserve detailed error messages
- Ensure actionable permission fix commands are included in error response
- Prevents detailed error messages from being lost in exception chain
* fix(config): Remove overly strict pre-write permission check
- Remove pre-write file existence/writability check that was blocking valid writes
- Let actual file write operation determine success/failure
- Provide detailed error messages only when write actually fails
- Fixes regression where config_secrets.json saves were blocked unnecessarily
* fix(config): Use atomic writes for config_secrets.json to handle permission issues
- Write to temp file first, then atomically move to final location
- Works even when existing file isn't writable (as long as directory is writable)
- Matches pattern used elsewhere in codebase (disk_cache, atomic_manager)
- Fixes permission errors when saving secrets configuration
* chore: Update music plugin submodule to include live_priority fix
* fix(plugins): Improve plugin ID determination in dynamic button generation
- Update generateFormFromSchema to pass currentPluginConfig?.pluginId and add data attributes
- Update generateSimpleConfigForm to pass currentPluginConfig?.pluginId and add data attributes
- Scope fallback 6 DOM lookup to button context instead of document-wide search
- Ensures correct plugin tab selection when multiple plugins are present
- Maintains existing try/catch error handling and logging
---------
Co-authored-by: Chuck <chuck@example.com>
|
||
|
|
1815a5b791 |
fix(fonts): Remove missing cozette.bdf font reference (#160)
- Removed cozette_bdf from common_fonts dictionary in font_manager.py - Removed cozette_bdf option from web interface font selectors - Resolves warning: 'Common font file not found: assets/fonts/cozette.bdf' - Font can be re-enabled by adding the font file and re-adding to common_fonts Co-authored-by: Chuck <chuck@example.com> |
||
|
|
97a301a1a9 |
fix(web-ui): Fix GitHub token warning persistence and improve UX (#154)
* fix(web): Resolve font display and config API error handling issues
- Fix font catalog display error where path.startsWith fails
(path is object, not string)
- Update save_main_config to use error_response() helper
- Improve save_raw_main_config error handling consistency
- Add proper error codes and traceback details to API responses
* fix(web): Prevent fontCatalog redeclaration error on HTMX reload
- Use window object to store global font variables
- Check if script has already loaded before declaring variables
- Update both window properties and local references on assignment
- Fixes 'Identifier fontCatalog has already been declared' error
* fix(web): Wrap fonts script in IIFE to prevent all redeclaration errors
- Wrap entire script in IIFE that only runs once
- Check if script already loaded before declaring variables/functions
- Expose initializeFontsTab to window for re-initialization
- Prevents 'Identifier has already been declared' errors on HTMX reload
* fix(web): Exempt config save API endpoints from CSRF protection
- Exempt save_raw_main_config, save_raw_secrets_config, and save_main_config from CSRF
- These endpoints are called via fetch from JavaScript and don't include CSRF tokens
- Fixes 500 error when saving config via raw JSON editor
* fix(web): Exempt system action endpoint from CSRF protection
- Exempt execute_system_action from CSRF
- Fixes 500 error when using system action buttons (restart display, restart Pi, etc.)
- These endpoints are called via HTMX and don't include CSRF tokens
* fix(web): Exempt all API v3 endpoints from CSRF protection
- Add before_request handler to exempt all api_v3.* endpoints
- All API endpoints are programmatic (HTMX/fetch) and don't include CSRF tokens
- Prevents future CSRF errors on any API endpoint
- Cleaner than exempting individual endpoints
* refactor(web): Remove CSRF protection for local-only application
- CSRF is designed for internet-facing apps to prevent cross-site attacks
- For local-only Raspberry Pi app, threat model is different
- All endpoints were exempted anyway, so it wasn't protecting anything
- Forms use HTMX without CSRF tokens
- If exposing to internet later, can re-enable with proper token implementation
* fix(web): Fix font path double-prefixing in font catalog display
- Only prefix with 'assets/fonts/' if path is a bare filename
- If path starts with '/' (absolute) or 'assets/' (already prefixed), use as-is
- Fixes double-prefixing when get_fonts_catalog returns relative paths like 'assets/fonts/press_start.ttf'
* fix(web): Remove fontsTabInitialized guard to allow re-initialization on HTMX reload
- Remove fontsTabInitialized check that prevented re-initialization on HTMX content swap
- The window._fontsScriptLoaded guard is sufficient to prevent function redeclaration
- Allow initializeFontsTab() to run on each HTMX swap to attach listeners to new DOM elements
- Fixes fonts UI breaking after HTMX reload (buttons, upload dropzone, etc. not working)
* fix(api): Preserve empty strings for optional string fields in plugin config
- Add _is_field_required() helper to check if fields are required in schema
- Update _parse_form_value_with_schema() to preserve empty strings for optional string fields
- Fixes 400 error when saving MQTT plugin config with empty username/password
- Resolves validation error: 'Expected type string, got NoneType'
* fix(config): Add defaults to schemas and fix None value handling
- Updated merge_with_defaults to replace None values with defaults
- Fixed form processing to skip empty optional fields without defaults
- Added script to automatically add defaults to all plugin config schemas
- Added defaults to 89 fields across 10 plugin schemas
- Prevents validation errors from None values in configs
Changes:
- schema_manager.py: Enhanced merge_with_defaults to replace None with defaults
- api_v3.py: Added _SKIP_FIELD sentinel to skip optional fields without defaults
- add_defaults_to_schemas.py: Script to add sensible defaults to schemas
- Plugin schemas: Added defaults for number, boolean, and array fields
* fix(config): Fix save button spinner by checking HTTP status code
- Fixed handleConfigSave to check xhr.status instead of event.detail.successful
- With hx-swap="none", HTMX doesn't set event.detail.successful
- Now properly detects successful saves (status 200-299) and stops spinner
- Improved error message extraction from API responses
- Also fixed handleToggleResponse for consistency
* fix(web-ui): Resolve GitHub token warning persistence after save
- Made checkGitHubAuthStatus() return Promise for proper async handling
- Clear sessionStorage dismissal flag when token is saved
- Add delay before status check to ensure backend token reload
- Wait for status check completion before hiding settings panel
Fixes issue where GitHub token warnings and pop-ups would not
disappear after successfully saving a token in the web UI.
* fix(web-ui): Add token validation and improve GitHub token warning behavior
- Add token validation to backend API endpoint to check if token is valid/expired
- Implement _validate_github_token() method in PluginStoreManager with caching
- Update frontend to show warning only when token is missing or invalid
- Keep settings panel accessible (collapsible) when token is configured
- Collapse settings panel content after successful token save instead of hiding
- Display specific error messages for invalid/expired tokens
- Clear sessionStorage dismissal flag when token becomes valid
Fixes issue where GitHub token warnings and settings panel would not
properly hide/show based on token status. Now validates token validity
and provides better UX with collapsible settings panel.
* fix(web-ui): Fix CSS/display issue for GitHub token warning and settings
- Update all hide/show operations to use both classList and style.display
- Fix checkGitHubAuthStatus() to properly hide/show warning and settings
- Fix dismissGithubWarning() to use both methods
- Fix toggleGithubTokenSettings() with improved state checking
- Fix collapse button handler with improved state checking
- Fix saveGithubToken() to properly show/collapse settings panel
This ensures elements actually hide/show when status changes, matching
the pattern used elsewhere in the codebase (like toggleSection). All
buttons (dismiss, close, collapse) should now work correctly.
* fix(web-ui): Fix GitHub token expand button functionality
- Convert collapse button handler to named function (toggleGithubTokenContent)
- Improve state checking using class, inline style, and computed style
- Re-attach event listener after saving token to ensure it works
- Add console logging for debugging
- Make function globally accessible for better reliability
Fixes issue where expand button didn't work after saving token.
* fix(web-ui): Remove X button and improve GitHub token panel behavior
- Remove X (close) button from GitHub token configuration panel
- Replace toggleGithubTokenSettings() with openGithubTokenSettings() that only opens
- Auto-collapse panel when token is valid (user must click expand to edit)
- Auto-detect token status on page load (no need to click save)
- Simplify saveGithubToken() to rely on checkGitHubAuthStatus() for UI updates
- Ensure expand button works correctly with proper event listener attachment
The panel now remains visible but collapsed when a token is configured,
allowing users to expand it when needed without the ability to completely hide it.
* refactor(web-ui): Improve GitHub token collapse button code quality
- Update comment to reflect actual behavior (prevent parent click handlers)
- Use empty string for display to defer to CSS instead of hard-coding block/none
- Extract duplicate clone-and-attach logic into attachGithubTokenCollapseHandler() helper
- Make helper function globally accessible for reuse in checkGitHubAuthStatus()
Improves maintainability and makes code more future-proof for layout changes.
* fix(web-ui): Fix collapse/expand button by using removeProperty for display
- Use style.removeProperty('display') instead of style.display = ''
- This properly removes inline styles and defers to CSS classes
- Fixes issue where collapse/expand button stopped working after refactor
* fix(web-ui): Make display handling consistent for token collapse
- Use removeProperty('display') consistently in all places
- Fix checkGitHubAuthStatus() to use removeProperty instead of inline style
- Simplify state checking to rely on hidden class with computed style fallback
- Ensures collapse/expand button works correctly by deferring to CSS classes
* fix(web-ui): Fix token collapse button and simplify state detection
- Simplify state checking to rely on hidden class only (element has class='block')
- Only remove inline display style if it exists (check before removing)
- Add console logging to debug handler attachment
- Ensure collapse/expand works by relying on CSS classes
Fixes issues where:
- Collapse button did nothing
- Auto-detection of token status wasn't working
* debug(web-ui): Add extensive debugging for token collapse button
- Add console logs to track function calls and element detection
- Improve state detection to use computed style as fallback
- Add wrapper function for click handler to ensure it's called
- Better error messages to identify why handler might not attach
This will help identify why the collapse button isn't working.
* debug(web-ui): Add comprehensive debugging for GitHub token features
- Add console logs to checkGitHubAuthStatus() to track execution
- Re-attach collapse handler after plugin store is rendered
- Add error stack traces for better debugging
- Ensure handler is attached when content is dynamically loaded
This will help identify why:
- Auto-detection of token status isn't working
- Collapse button isn't functioning
* fix(web-ui): Move checkGitHubAuthStatus before IIFE to fix scope issue
- Move checkGitHubAuthStatus function definition before IIFE starts
- Function was defined after IIFE but called inside it, causing it to be undefined
- Now function is available when called during initialization
- This should fix auto-detection of token status on page load
* debug(web-ui): Add extensive logging to GitHub token functions
- Add logging when checkGitHubAuthStatus is defined
- Add logging when function is called during initialization
- Add logging in attachGithubTokenCollapseHandler
- Add logging in store render callback
- This will help identify why functions aren't executing
* fix(web-ui): Move GitHub token functions outside IIFE for availability
- Move attachGithubTokenCollapseHandler and toggleGithubTokenContent outside IIFE
- These functions need to be available when store renders, before IIFE completes
- Add logging to initializePlugins to track when it's called
- This should fix the 'undefined' error when store tries to attach handlers
* fix(web-ui): Fix GitHub token content collapse/expand functionality
- Element has 'block' class in HTML which conflicts with 'hidden' class
- When hiding: add 'hidden', remove 'block', set display:none inline
- When showing: remove 'hidden', add 'block', remove inline display
- This ensures proper visibility toggle for the GitHub API Configuration section
* perf(web-ui): Optimize GitHub token detection speed
- Call checkGitHubAuthStatus immediately when script loads (if elements exist)
- Call it early in initPluginsPage (before full initialization completes)
- Use requestAnimationFrame instead of setTimeout(100ms) for store render callback
- Reduce save token delay from 300ms to 100ms
- Token detection now happens in parallel with other initialization tasks
- This makes token status visible much faster on page load
* fix(web-ui): Fix all collapse/expand buttons on plugins page
- Fix Installed Plugins section collapse/expand button
- Fix Plugin Store section collapse/expand button
- Fix GitHub Install section collapse/expand button
- Apply same fixes as GitHub token button:
* Clone buttons to remove existing listeners
* Handle block/hidden class conflicts properly
* Add proper event prevention (stopPropagation/preventDefault)
* Add logging for debugging
- All collapse/expand buttons should now work correctly
* fix(web-ui): Fix syntax error in setupGitHubInstallHandlers
- Ensure all handler setup code is inside the function
- Add comment to mark function end clearly
* refactor(web-ui): Remove collapse buttons from Installed Plugins and Plugin Store
- Remove collapse/expand buttons from Installed Plugins section
- Remove collapse/expand buttons from Plugin Store section
- Remove related JavaScript handler code
- These sections are now always visible for better UX
- GitHub token section still has collapse functionality
---------
Co-authored-by: Chuck <chuck@example.com>
|
||
|
|
7ec4323ff4 |
fix(config): Add defaults to schemas and fix config validation issues (#153)
* fix(web): Resolve font display and config API error handling issues - Fix font catalog display error where path.startsWith fails (path is object, not string) - Update save_main_config to use error_response() helper - Improve save_raw_main_config error handling consistency - Add proper error codes and traceback details to API responses * fix(web): Prevent fontCatalog redeclaration error on HTMX reload - Use window object to store global font variables - Check if script has already loaded before declaring variables - Update both window properties and local references on assignment - Fixes 'Identifier fontCatalog has already been declared' error * fix(web): Wrap fonts script in IIFE to prevent all redeclaration errors - Wrap entire script in IIFE that only runs once - Check if script already loaded before declaring variables/functions - Expose initializeFontsTab to window for re-initialization - Prevents 'Identifier has already been declared' errors on HTMX reload * fix(web): Exempt config save API endpoints from CSRF protection - Exempt save_raw_main_config, save_raw_secrets_config, and save_main_config from CSRF - These endpoints are called via fetch from JavaScript and don't include CSRF tokens - Fixes 500 error when saving config via raw JSON editor * fix(web): Exempt system action endpoint from CSRF protection - Exempt execute_system_action from CSRF - Fixes 500 error when using system action buttons (restart display, restart Pi, etc.) - These endpoints are called via HTMX and don't include CSRF tokens * fix(web): Exempt all API v3 endpoints from CSRF protection - Add before_request handler to exempt all api_v3.* endpoints - All API endpoints are programmatic (HTMX/fetch) and don't include CSRF tokens - Prevents future CSRF errors on any API endpoint - Cleaner than exempting individual endpoints * refactor(web): Remove CSRF protection for local-only application - CSRF is designed for internet-facing apps to prevent cross-site attacks - For local-only Raspberry Pi app, threat model is different - All endpoints were exempted anyway, so it wasn't protecting anything - Forms use HTMX without CSRF tokens - If exposing to internet later, can re-enable with proper token implementation * fix(web): Fix font path double-prefixing in font catalog display - Only prefix with 'assets/fonts/' if path is a bare filename - If path starts with '/' (absolute) or 'assets/' (already prefixed), use as-is - Fixes double-prefixing when get_fonts_catalog returns relative paths like 'assets/fonts/press_start.ttf' * fix(web): Remove fontsTabInitialized guard to allow re-initialization on HTMX reload - Remove fontsTabInitialized check that prevented re-initialization on HTMX content swap - The window._fontsScriptLoaded guard is sufficient to prevent function redeclaration - Allow initializeFontsTab() to run on each HTMX swap to attach listeners to new DOM elements - Fixes fonts UI breaking after HTMX reload (buttons, upload dropzone, etc. not working) * fix(api): Preserve empty strings for optional string fields in plugin config - Add _is_field_required() helper to check if fields are required in schema - Update _parse_form_value_with_schema() to preserve empty strings for optional string fields - Fixes 400 error when saving MQTT plugin config with empty username/password - Resolves validation error: 'Expected type string, got NoneType' * fix(config): Add defaults to schemas and fix None value handling - Updated merge_with_defaults to replace None values with defaults - Fixed form processing to skip empty optional fields without defaults - Added script to automatically add defaults to all plugin config schemas - Added defaults to 89 fields across 10 plugin schemas - Prevents validation errors from None values in configs Changes: - schema_manager.py: Enhanced merge_with_defaults to replace None with defaults - api_v3.py: Added _SKIP_FIELD sentinel to skip optional fields without defaults - add_defaults_to_schemas.py: Script to add sensible defaults to schemas - Plugin schemas: Added defaults for number, boolean, and array fields * fix(config): Fix save button spinner by checking HTTP status code - Fixed handleConfigSave to check xhr.status instead of event.detail.successful - With hx-swap="none", HTMX doesn't set event.detail.successful - Now properly detects successful saves (status 200-299) and stops spinner - Improved error message extraction from API responses - Also fixed handleToggleResponse for consistency --------- Co-authored-by: Chuck <chuck@example.com> |
||
|
|
d14b5ffb8f |
fix(web): Resolve font display errors and config API CSRF issues (#152)
* fix(web): Resolve font display and config API error handling issues - Fix font catalog display error where path.startsWith fails (path is object, not string) - Update save_main_config to use error_response() helper - Improve save_raw_main_config error handling consistency - Add proper error codes and traceback details to API responses * fix(web): Prevent fontCatalog redeclaration error on HTMX reload - Use window object to store global font variables - Check if script has already loaded before declaring variables - Update both window properties and local references on assignment - Fixes 'Identifier fontCatalog has already been declared' error * fix(web): Wrap fonts script in IIFE to prevent all redeclaration errors - Wrap entire script in IIFE that only runs once - Check if script already loaded before declaring variables/functions - Expose initializeFontsTab to window for re-initialization - Prevents 'Identifier has already been declared' errors on HTMX reload * fix(web): Exempt config save API endpoints from CSRF protection - Exempt save_raw_main_config, save_raw_secrets_config, and save_main_config from CSRF - These endpoints are called via fetch from JavaScript and don't include CSRF tokens - Fixes 500 error when saving config via raw JSON editor * fix(web): Exempt system action endpoint from CSRF protection - Exempt execute_system_action from CSRF - Fixes 500 error when using system action buttons (restart display, restart Pi, etc.) - These endpoints are called via HTMX and don't include CSRF tokens * fix(web): Exempt all API v3 endpoints from CSRF protection - Add before_request handler to exempt all api_v3.* endpoints - All API endpoints are programmatic (HTMX/fetch) and don't include CSRF tokens - Prevents future CSRF errors on any API endpoint - Cleaner than exempting individual endpoints * refactor(web): Remove CSRF protection for local-only application - CSRF is designed for internet-facing apps to prevent cross-site attacks - For local-only Raspberry Pi app, threat model is different - All endpoints were exempted anyway, so it wasn't protecting anything - Forms use HTMX without CSRF tokens - If exposing to internet later, can re-enable with proper token implementation * fix(web): Fix font path double-prefixing in font catalog display - Only prefix with 'assets/fonts/' if path is a bare filename - If path starts with '/' (absolute) or 'assets/' (already prefixed), use as-is - Fixes double-prefixing when get_fonts_catalog returns relative paths like 'assets/fonts/press_start.ttf' * fix(web): Remove fontsTabInitialized guard to allow re-initialization on HTMX reload - Remove fontsTabInitialized check that prevented re-initialization on HTMX content swap - The window._fontsScriptLoaded guard is sufficient to prevent function redeclaration - Allow initializeFontsTab() to run on each HTMX swap to attach listeners to new DOM elements - Fixes fonts UI breaking after HTMX reload (buttons, upload dropzone, etc. not working) --------- Co-authored-by: Chuck <chuck@example.com> |
||
|
|
33e4f3680c |
fix(web): Preserve API error messages in config save handlers (#151)
- Refactor error handling to use async/await for clearer flow - Store response status before JSON parsing to preserve context - Extract specific error messages from API responses (data.message) - Only use generic HTTP status messages when JSON parsing fails - Ensure API error messages like validation failures are shown to users instead of generic 'HTTP 500: Internal Server Error' Fixes issue where specific API error messages were being replaced with generic HTTP status messages in the catch handler. Co-authored-by: Chuck <chuck@example.com> |
||
|
|
b0d65581df |
Fix/config save error handling (#150)
* fix(docs): Add trailing newlines to documentation files * fix(web): Resolve font configuration loading error on first page load - Remove ineffective DOMContentLoaded listener from fonts partial (loads via HTMX after main page DOMContentLoaded) - Add proper HTMX event handling with htmx:afterSettle for reliable initialization - Add duplicate initialization protection flag - Improve error handling with response validation and clearer error messages - Add fallback initialization check for edge cases - Ensure DOM elements exist before attempting initialization Fixes issue where 'Error loading font configuration' appeared on first web UI load when opening fonts tab. * fix(config): Update plugins_directory to plugin-repos in config template The web-ui-info plugin is located in plugin-repos/ directory, but the config template was pointing to plugins/ directory. This caused the plugin to not be discovered on fresh installations. - Changed plugins_directory from 'plugins' to 'plugin-repos' in config.template.json - Matches actual plugin location and code default behavior - Ensures web-ui-info plugin is available by default on fresh installs * fix(config): Improve config save error handling - Make load_config() failure non-fatal in save_raw_file_content - Wrapped reload in try-except to prevent save failures when reload fails - File save is atomic and successful even if reload fails - Logs warning when reload fails but doesn't fail the operation - Improve error messages in API endpoints - Added detailed error logging with full traceback for debugging - Extract specific error messages from ConfigError exceptions - Include config_path in error messages when available - Provide fallback messages for empty error strings - Enhance frontend error handling - Check response status before parsing JSON - Better handling of non-JSON error responses - Fallback error messages if error details are missing Fixes issue where 'Error saving config.json: an error occured' was shown even when the file was saved successfully but reload failed. --------- Co-authored-by: Chuck <chuck@example.com> |
||
|
|
f412350110 |
Fix/fonts loading error (#148)
* fix(docs): Add trailing newlines to documentation files * fix(web): Resolve font configuration loading error on first page load - Remove ineffective DOMContentLoaded listener from fonts partial (loads via HTMX after main page DOMContentLoaded) - Add proper HTMX event handling with htmx:afterSettle for reliable initialization - Add duplicate initialization protection flag - Improve error handling with response validation and clearer error messages - Add fallback initialization check for edge cases - Ensure DOM elements exist before attempting initialization Fixes issue where 'Error loading font configuration' appeared on first web UI load when opening fonts tab. --------- Co-authored-by: Chuck <chuck@example.com> |