- api_v3.py install_plugin_requirements: replace hardcoded plugin-repos
fallback with config-driven resolution (plugin_system.plugins_directory),
matching the pattern used elsewhere in the module
- api_v3.py _fix_json_arrays: recurse into converted and existing array
elements when items.type is object, so nested numeric-keyed dicts inside
array items are also normalized
- tools.html toolsAction: check r.ok before r.json() and recover
gracefully from non-JSON error bodies (HTML 500 pages), consistent
with the existing loadGitInfo guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- first_time_install.sh: add _clone_rpi_rgb() wrapper so retry() cleans
up any partial rpi-rgb-led-matrix-master dir before each clone attempt
- first_time_install.sh: use apt-get -o DPkg::Lock::Timeout=180 so apt
handles lock contention natively instead of relying solely on flock TOCTOU check
- install_dependencies_apt.py: pass DPkg::Lock::Timeout=180 to apt-get
install to avoid failing when unattended-upgrades holds the lock
- install_dependencies_apt.py: add type annotations to all public helpers
- api_v3.py: fix install_plugin_requirements to read plugin_manager from
api_v3 blueprint attribute instead of the always-None module variable
- tools.html: loadGitInfo() now checks r.ok before parsing JSON and
surfaces d.status === 'error' with the server's message in the panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(web): add Tools tab and row address type setting
Adds a Tools/Utilities tab to the web interface with one-click
maintenance buttons that previously required SSH:
- Git status panel (branch, dirty state, recent commits)
- Pull latest (rebase) and force reset to origin/main
- Reinstall base requirements (pip, with output)
- Reinstall per-plugin requirements (pass/fail per plugin)
- Clear __pycache__ directories
- Quick-access restart for display and web services
Also exposes the hzeller row_address_type option (0–4) in the
Display settings tab. The backend already read this value from
config; the UI, API field list, and validation were missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tools-tab): address code review findings
- Add _GIT = shutil.which('git') alongside _SUDO/_JOURNALCTL; return
503 in force_git_reset and get_git_info if git is unavailable
- Check git branch/status returncodes in get_git_info(); return a clear
500 error instead of silently treating a failed run as a clean repo
- Cap pip stdout+stderr at 50 KB via _truncate_output() helper to
avoid OOM on verbose dependency resolution or build failures
- Scrub embedded HTTPS credentials from remote_url via
_scrub_git_remote_url() using urllib.parse before returning to UI
- Fix clear_pycache to track and report failed deletions separately
instead of counting them as successes (removed ignore_errors=True,
wrapped in try/except OSError)
Skipped: plugin_manager-vs-api_v3.plugin_manager (api_v3 is the
Blueprint object; accessing .plugin_manager on it would fail — module-
level variable is the correct pattern used throughout this blueprint);
pages_v3 broad-except (identical to every other _load_*_partial in the
file); base.html HTMX fallback (loadTabContent handles all tabs
generically; named fallbacks only exist for tabs needing JS re-init);
tools.html auth (pre-existing architectural decision — reboot/shutdown
on the same endpoint are also unauthenticated).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tools-tab): resolve remaining PR review comments
- api_v3: use getattr(api_v3, 'plugin_manager', None) instead of the
module-level plugin_manager (always None); app.py sets the blueprint
attribute, not the module global, so the fallback to plugin-repos was
always taken
- pages_v3: replace broad except Exception in _load_tools_partial with
specific TemplateNotFound / OSError handlers and add [Pages V3][Tools]
context prefix to log messages and error responses for easier Pi
debugging
- base.html: add Tools tab branch to the HTMX-unavailable fallback block
in loadTabContent so the tab loads gracefully via direct fetch if HTMX
never initialises
Skipped: auth on execute_system_action — pre-existing app-wide design;
reboot/shutdown and all other system actions share the same exposure.
An app-level auth layer is the correct fix and is out of scope here.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tools-tab): resolve second-pass review findings
- Wrap per-plugin subprocess.run in try/except TimeoutExpired/OSError so
one plugin's failure appends a result entry and continues the loop
rather than collapsing the whole batch into a 500
- Validate double_sided_copies divisibility against chain_length
(horizontal axis) or parallel (vertical axis) after the range check;
reads effective axis from the current request or stored config
- Exclude double_sided_fields from the generic key-merge loop so
double_sided_enabled/copies/axis are never written as root-level keys
- Fix tools.html copy: "then restores the stash" removed — git_pull
stashes changes but never pops them
- Check r.ok and d.status in loadGitInfo before building the panel;
backend error messages now surface instead of silently showing a
false-clean state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tools-tab): don't expose filesystem paths in OSError messages
CodeQL flagged str(exc) flowing into the JSON response for the
install_plugin_requirements action. Use exc.strerror instead, which
gives the OS error description ("No such file or directory",
"Permission denied") without the internal filesystem path.
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>