On a device running four installed, configured, working plugins, the overview
banner read:
Stale plugin config entries found: football-scoreboard, odds-ticker, data,
ledmatrix-weather, starlark-apps. Remove them from config.json or reinstall
via the Plugin Store.
Every claim in that sentence was wrong, and following its advice would have
deleted 4.9KB of working league settings. Four separate defects combined.
1. Secrets keys became phantom plugins. load_config() merges
config_secrets.json into the config it returns, and the ignore list named
only 'github' and 'youtube'. A 'data' key in that file therefore read as a
plugin id and was reported as "in config but not on disk" forever. Read the
secrets file's own top-level keys instead of hardcoding two of them.
2. The auto-fix clobbered real config. The handler for "on disk but not in
config" assigned `config[plugin_id] = {'enabled': False}` unconditionally,
so whenever detection was wrong it replaced a plugin's entire configuration
with a stub. On the reported device it only failed to do so because the
write hit EACCES. Now it refuses to overwrite an entry that already exists.
3. The banner gave backwards advice. plugin_missing_in_config ("on disk, not in
config") and plugin_missing_on_disk ("in config, not on disk") are opposite
problems, and both were rendered as "stale config entries ... remove them
from config.json" -- which is correct for the second and destructive for the
first. They are now reported separately, each with the advice that fits.
4. A stale verdict was served indefinitely. The result is a snapshot written
once per run to a status file, and a run that fails to apply a fix also
declares it will not retry. A condition that had since resolved kept being
reported for hours. The status endpoint now re-checks stored findings
against current state, dropping only what it can prove stale and keeping
any kind it cannot re-verify.
The secrets-key lookup is deliberately fail-safe: an unreadable, absent,
malformed or non-path secrets location narrows the ignore set rather than
raising. An earlier revision let TypeError escape, which the broad handler in
_get_config_state() swallowed as "Error reading config state" -- emptying the
config state and making every downstream detection wrong. The existing
reconciliation tests caught it; there is now a regression test for it too.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
LED Matrix Web Interface V3
Modern, production web interface for controlling the LED Matrix display.
Overview
This directory contains the active V3 web interface with the following features:
- Real-time display preview via Server-Sent Events (SSE)
- Plugin management and configuration
- System monitoring and logs
- Modern, responsive UI
- RESTful API
Directory Structure
web_interface/
├── app.py # Main Flask application
├── start.py # Startup script
├── run.sh # Shell runner script
├── requirements.txt # Python dependencies
├── blueprints/ # Flask blueprints
│ ├── api_v3.py # API endpoints
│ └── pages_v3.py # Page routes
├── templates/ # HTML templates
│ └── v3/
│ ├── base.html
│ ├── index.html
│ └── partials/
└── static/ # CSS/JS assets
└── v3/
├── app.css
├── app.js
├── manifest.json # PWA manifest
├── plugins_manager.js
├── icons/ # PWA / touch icons
├── js/ # Alpine, htmx, app shell, widgets, utils
└── vendor/ # codemirror, fontawesome
Running the Web Interface
Standalone (Development)
From the project root:
python3 web_interface/start.py
Or using the shell script:
./web_interface/run.sh
As a Service (Production)
The web interface can run as a systemd service that starts automatically based on the web_display_autostart configuration setting:
sudo systemctl start ledmatrix-web
sudo systemctl enable ledmatrix-web # Start on boot
Accessing the Interface
Once running, access the web interface at:
- Local: http://localhost:5000
- Network: http://:5000
Configuration
The web interface reads configuration from:
config/config.json- Main configurationconfig/config_secrets.json- API keys and secrets
API Documentation
The V3 API is mounted at /api/v3/ (app.py:144). For the complete
list and request/response formats, see
docs/REST_API_REFERENCE.md. Quick
reference for the most common endpoints:
Configuration
GET /api/v3/config/main- Get main configurationPOST /api/v3/config/main- Save main configurationGET /api/v3/config/secrets- Get secrets configurationPOST /api/v3/config/raw/main- Save raw main config (Config Editor)POST /api/v3/config/raw/secrets- Save raw secrets
Display & System Control
GET /api/v3/system/status- System statusPOST /api/v3/system/action- Control display (action body:start_display,stop_display,restart_display_service,restart_web_service,git_pull,reboot_system,shutdown_system,enable_autostart,disable_autostart)GET /api/v3/display/current- Current display frameGET /api/v3/display/on-demand/status- On-demand statusPOST /api/v3/display/on-demand/start- Trigger on-demand displayPOST /api/v3/display/on-demand/stop- Clear on-demand
Plugins
GET /api/v3/plugins/installed- List installed pluginsGET /api/v3/plugins/config?plugin_id=<id>- Get plugin configPOST /api/v3/plugins/config- Update plugin configurationGET /api/v3/plugins/schema?plugin_id=<id>- Get plugin schemaPOST /api/v3/plugins/toggle- Enable/disable pluginPOST /api/v3/plugins/install- Install from registryPOST /api/v3/plugins/install-from-url- Install from GitHub URLPOST /api/v3/plugins/uninstall- Uninstall pluginPOST /api/v3/plugins/update- Update plugin
Plugin Store
GET /api/v3/plugins/store/list- List available registry pluginsGET /api/v3/plugins/store/github-status- GitHub authentication statusPOST /api/v3/plugins/store/refresh- Refresh registry from GitHub
Real-time Streams (SSE)
SSE stream endpoints are defined directly on the Flask app
(app.py:607-619 — includes the CSRF exemption and rate-limit hookup
alongside the three route definitions), not on the api_v3 blueprint:
GET /api/v3/stream/stats- System statistics streamGET /api/v3/stream/display- Display preview streamGET /api/v3/stream/logs- Service logs stream
Development
When making changes to the web interface:
- Edit files in this directory
- Test changes by running
python3 web_interface/start.py - Restart the service if running:
sudo systemctl restart ledmatrix-web
Notes
- Templates and static files use the
v3/prefix to allow for future versions - The interface uses Flask blueprints for modular organization
- SSE streams provide real-time updates without polling