Five review findings, plus the two bandit reported.
Config variable keys were checked against an identifier regex only.
Python keywords slipped past it and were caught downstream by ast.parse,
but reported as
Generated code has a syntax error: invalid syntax (<unknown>, line 17)
which names neither the field nor the value. They are now refused by
name, soft keywords ('match', 'case') included.
Worse, a key matching a BasePlugin attribute generated *valid* code that
silently clobbered plugin state. 'config' is the sharp one: the
assignment lands immediately after super().__init__(), so
self.config = config.get("config", "x")
replaces the plugin's config dict with a string, and every later
self.config.get(...) fails at runtime. Refused now, along with logger,
display_manager, cache_manager, plugin_id, enabled, self and the
lifecycle method names. A test pins the ordering assumption that reserved
list rests on, so it fails if config vars are ever emitted before
super().__init__() instead.
Also:
- The silent `except Exception: pass` around manifest parsing now logs.
It left "partial import produced nothing" indistinguishable from a
malformed manifest. (bandit B110)
- list_plugins() called iterdir() on a directory that may not exist --
a fresh install or a bad path returned 500 instead of an empty list.
- metadata.id is stripped in the two route handlers, matching
_generate_plugin_files, which strips before validating. Without it
" my-plugin " generated fine and then failed the id check at install,
reading as a generator bug.
- The jinja Environment's autoescape=False now says why: these templates
emit Python, and escaping a quote to " inside generated code would
break it. Safety comes from the values instead -- _safe_int, _rgb_expr
and _reject_source_breaking, all covered by the injection suite.
(bandit B701, marked nosec with that rationale)
bandit on composer.py: 2 findings -> 0.
Verified: 156 tests across the two composer suites. Removing either new
key check fails 9.
Not reproduced: the suggestion to emit `pass` so a conditional block is
never empty. 'line' and 'divider' render through a different template
branch and 'section' emits nothing at all, so no element type available
here produces an `if width >= N:` with an empty body. Left alone rather
than changing template output speculatively.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
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
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