* refactor(web): split api_v3/plugins.py by area
web_interface/blueprints/api_v3/plugins.py (3,285 lines) becomes:
- plugins.py: installed list, enable/disable, plugin actions
- plugin_store.py: install, update, uninstall, store, saved repositories
- plugin_config.py: config get/save, schema, reset
- plugin_assets.py: asset uploads and plugin static files
- plugin_health.py: health, metrics, limits
- plugin_operations.py: operation history, state reconciliation
- plugin_calendar.py: calendar credentials and auth
Pure move: all 44 functions and 38 route decorators are byte-identical
(checked with ast), URLs and endpoint names are unchanged (url-map test).
Each module imports only what it uses. Tests and config.py that reached
into plugins.py for moved names now import from the new module.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(web): keep exception text out of calendar responses; annotate moved code
The split made scanners report existing findings in the moved code as new:
- CodeQL: the calendar auth and calendar-list routes returned exception
text (redacted, but still derived from the exception). Both now log the
exception and return a fixed message pointing at the log.
- MD5 in the asset upload only makes a filename unique: usedforsecurity=False.
- pickle reads/writes the calendar plugin's own OAuth token (as before):
annotated. Token-status labels and a log line naming the secrets path are
false positives: annotated with the repo's nosec/nosemgrep convention.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(web): name uploaded assets with SHA-256 instead of MD5
The hash only makes an uploaded image's filename unique. Codacy flags MD5
even with usedforsecurity=False, and SHA-256 does the job as well; existing
files keep their names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(web): keep the redacted exception detail in calendar errors
Reverts the calendar part of 5e695b7c. The project's policy
(test_no_api_v3_handler_discards_its_exception) is that an API error
carries the redacted exception detail -- describe_exception runs it
through the credential redactor -- so a failure is diagnosable from the web
UI. Dropping it for CodeQL broke that; CodeQL can't see the redaction, so
its two alerts here are false positives, like the existing ones on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
8.0 KiB
Plugin Configuration Tabs
Overview
Each installed plugin now gets its own dedicated configuration tab in the web interface. This provides a clean, organized way to configure plugins without cluttering the Plugin Manager tab.
Features
- Automatic Tab Generation: When a plugin is installed, a new tab is automatically created in the web UI
- JSON Schema-Based Forms: Configuration forms are automatically generated based on each plugin's
config_schema.json - Type-Safe Inputs: Form inputs are created based on the JSON Schema type (boolean, number, string, array, enum)
- Default Values: All fields show current values or fallback to schema defaults
- Real-Time Validation: Input constraints from JSON Schema are enforced (min, max, maxLength, etc.)
User Experience
Accessing Plugin Configuration
- Navigate to the Plugin Manager tab to see all installed plugins
- Click the Configure button on any plugin card
- You'll be automatically taken to that plugin's configuration tab
- Alternatively, click directly on the plugin's tab button in the second nav row
Configuring a Plugin
- Open the plugin's configuration tab
- Modify settings using the generated form
- Click Save Configuration. The settings apply to the running display
without a restart: the display service reloads
config.jsonwhen it changes and calls the plugin'son_config_change()
The tab also has Refresh (reload the form), Update (update the plugin) and Uninstall buttons.
Plugin Manager vs Per-Plugin Configuration
- Plugin Manager tab (second nav row): used for browsing the Plugin Store, installing plugins, toggling installed plugins on/off, and updating/uninstalling them
- Per-plugin tabs (one per installed plugin, also in the second
nav row): used for configuring that specific plugin's behavior and
settings via a form auto-generated from its
config_schema.json
For Plugin Developers
Requirements
Every installed plugin gets a tab. To get a generated form in it, include a
config_schema.json file in the plugin's directory. The name is fixed: the
web interface finds the schema by that file name (SchemaManager in
src/plugin_system/schema_manager.py), and no manifest field points to it.
Note: You can optionally specify a Font Awesome icon class for your
plugin tab in manifest.json. See Plugin Custom Icons Guide for details.
Supported JSON Schema Types
The form generator supports the following JSON Schema types:
Boolean
{
"type": "boolean",
"default": true,
"description": "Enable or disable this feature"
}
Renders as: Toggle switch
Number / Integer
{
"type": "integer",
"default": 60,
"minimum": 1,
"maximum": 300,
"description": "Update interval in seconds"
}
Renders as: Number input with min/max constraints
String
{
"type": "string",
"default": "Hello, World!",
"minLength": 1,
"maxLength": 50,
"description": "The message to display"
}
Renders as: Text input with length constraints
Array
{
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 255
},
"minItems": 3,
"maxItems": 3,
"default": [255, 255, 255],
"description": "RGB color [R, G, B]"
}
Renders as: Text input (comma-separated values)
Example input: 255, 128, 0
Enum (Select)
{
"type": "string",
"enum": ["small", "medium", "large"],
"default": "medium",
"description": "Display size"
}
Renders as: Dropdown select
Example config_schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "My Plugin Configuration",
"description": "Configure my awesome plugin",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable this plugin"
},
"message": {
"type": "string",
"default": "Hello!",
"minLength": 1,
"maxLength": 50,
"description": "The message to display"
},
"update_interval": {
"type": "integer",
"default": 60,
"minimum": 1,
"maximum": 3600,
"description": "Update interval in seconds"
},
"color": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 255
},
"minItems": 3,
"maxItems": 3,
"default": [255, 255, 255],
"description": "RGB color [R, G, B]"
},
"mode": {
"type": "string",
"enum": ["scroll", "static", "fade"],
"default": "scroll",
"description": "Display mode"
}
},
"required": ["enabled"],
"additionalProperties": false
}
Best Practices
- Use Descriptive Labels: The
descriptionfield is shown as help text under each input - Set Sensible Defaults: Always provide default values that work out of the box
- Use Constraints: Leverage min/max, minLength/maxLength to guide users
- Mark Required Fields: Use the
requiredarray in your schema - Organize Properties: List properties in order of importance
Form Generation Process
Forms are rendered on the server, not generated in the browser:
- The web UI loads installed plugins via
/api/v3/plugins/installedand adds a tab button for each one - Opening a tab loads
/v3/partials/plugin-config/<plugin_id>(web_interface/blueprints/pages_v3.py), which loads the plugin's schema throughSchemaManagerand its current values fromconfig.json web_interface/templates/v3/partials/plugin_config.htmlrenders the form from the schema (widgets named byx-widgetare rendered by the scripts inweb_interface/static/v3/js/widgets/)- Save Configuration posts the form to
/api/v3/plugins/config(web_interface/blueprints/api_v3/plugin_config.py), which validates it against the schema, writesconfig.json(secret fields go toconfig_secrets.json) and shows a notification
Troubleshooting
Plugin Tab Not Appearing
- Check that the plugin is installed and appears in the Plugin Manager tab
- Check browser console for errors
- Reload the page
Form Not Generating Correctly
- Ensure
config_schema.jsonexists in the plugin directory - Validate your
config_schema.jsonagainst JSON Schema Draft 07 - Check that all properties have a
typefield - Ensure
defaultvalues match the specified type - Look for JavaScript errors in browser console
Configuration Not Saving
- Ensure the plugin is properly installed
- Check that config keys match schema properties
- Verify backend API is accessible
- Check browser network tab for API errors
Migration Guide
For Existing Plugins
If your plugin already has a config_schema.json:
- No changes needed! The tab will be automatically generated.
- Test the generated form to ensure all fields render correctly.
- Consider adding more descriptive
descriptionfields.
If your plugin doesn't have a config schema:
- Create
config_schema.jsonbased on your current config structure - Add descriptions for each property
- Set appropriate defaults
- Add validation constraints (min, max, etc.)
Backward Compatibility
- Plugins without
config_schema.jsonstill work normally - Their tab shows plain text, number and checkbox inputs for the keys already
in their
config.jsonsection, or "No configuration options available for this plugin." when there are none - Users can still edit config via the Raw JSON editor
Beyond the Basic Types
Nested objects (rendered as collapsible sections), x-widget widgets such as
color-picker and file-upload, and more are supported; see
PLUGIN_CONFIGURATION_GUIDE.md and
web_interface/static/v3/js/widgets/README.md.
Example Plugins
See these plugins for examples of config schemas:
hello-world: Simple plugin with basic typesclock-simple: Plugin with enum and number types
Support
For questions or issues:
- Check the main LEDMatrix wiki
- Review plugin documentation
- Open an issue on GitHub
- Join the community Discord