Files
LEDMatrix/web_interface
sarjent 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>
2026-05-14 18:09:33 -04:00
..
2025-12-27 14:15:49 -05:00
2025-12-27 14:15:49 -05:00
2025-12-27 14:15:49 -05:00
2025-12-27 14:15:49 -05:00
2025-12-27 14:15:49 -05:00

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:

Configuration

The web interface reads configuration from:

  • config/config.json - Main configuration
  • config/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 configuration
  • POST /api/v3/config/main - Save main configuration
  • GET /api/v3/config/secrets - Get secrets configuration
  • POST /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 status
  • POST /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 frame
  • GET /api/v3/display/on-demand/status - On-demand status
  • POST /api/v3/display/on-demand/start - Trigger on-demand display
  • POST /api/v3/display/on-demand/stop - Clear on-demand

Plugins

  • GET /api/v3/plugins/installed - List installed plugins
  • GET /api/v3/plugins/config?plugin_id=<id> - Get plugin config
  • POST /api/v3/plugins/config - Update plugin configuration
  • GET /api/v3/plugins/schema?plugin_id=<id> - Get plugin schema
  • POST /api/v3/plugins/toggle - Enable/disable plugin
  • POST /api/v3/plugins/install - Install from registry
  • POST /api/v3/plugins/install-from-url - Install from GitHub URL
  • POST /api/v3/plugins/uninstall - Uninstall plugin
  • POST /api/v3/plugins/update - Update plugin

Plugin Store

  • GET /api/v3/plugins/store/list - List available registry plugins
  • GET /api/v3/plugins/store/github-status - GitHub authentication status
  • POST /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 stream
  • GET /api/v3/stream/display - Display preview stream
  • GET /api/v3/stream/logs - Service logs stream

Development

When making changes to the web interface:

  1. Edit files in this directory
  2. Test changes by running python3 web_interface/start.py
  3. 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