Both shipped in #538 with no interface at all: the bridge could only be configured by editing integrations/mqtt_bridge/bridge_config.json over SSH, and the Pixlet editor was a script you had to know existed. Neither appeared anywhere in the web UI. They are now two sections in the Tools tab. **MQTT bridge.** Service state (installed / running / stopped), install, start, stop and restart, and a form for every setting: broker host and port, username, TLS, command topic, client id, API base, request timeout, on-demand duration and log level. The password is write-only. GET returns every other field plus a password_set flag, never the value -- this interface has no authentication, so anything it hands back is readable by anyone who can reach the port. Submitting a blank password keeps whatever is stored; there is an explicit Clear for removing one. The file is written through a temp file in the same directory and chmod 0600, so a crash mid-write cannot leave a config the bridge would refuse to load. Values are range-checked server-side (port, timeouts, log level, http(s) API base). **Pixlet editor.** Lists the apps on disk and starts a session per app, with a banner showing which app is open, a link to the editor, a countdown, and Stop. The banner survives a page reload because status is polled while a session runs, and Edit is disabled while one is open. Two changes to the script make that safe to drive from a browser: - It binds 0.0.0.0 by default (PIXLET_EDITOR_HOST overrides). Loopback-only meant the URL shown in a browser was unreachable and needed an SSH tunnel, which is the confusing path. The no-auth argument for loopback does not hold when web_interface already serves 0.0.0.0:5000 unauthenticated and can reconfigure everything. - The session ends by itself after PIXLET_EDITOR_TIMEOUT (default 30 min), enforced by `timeout` inside the script rather than by the caller. The display is stopped while editing, so a forgotten session would otherwise leave the panel dark and read as broken hardware -- the timeout is the whole reason a Start button is defensible. Two bugs found while testing that lifecycle, both of which would have bitten the Stop button: - GNU timeout runs its child in a NEW process group, so signalling the script's group killed only bash and orphaned pixlet with the port still bound. Now --foreground keeps one group, and the EXIT trap kills the recorded child too. - The liveness check treated a zombie as running: the script is a child of the web process and stays unreaped after exiting, and signal 0 succeeds against a zombie, so a finished session would have read as running forever. It now reaps non-blockingly and falls back to /proc state for a process that is not ours. Covered by test/js/dom/test_tools_sections.js (26 assertions, real DOM against the server-rendered partial): form prefill, the blank-means-unchanged password contract, the running-session banner and countdown, and that the editor link uses the host the page was loaded from. Also chmod +x on both scripts, which the docs tell you to run directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
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