feat(web): add Plugin Composer -- visual drag-and-drop plugin builder - #413
feat(web): add Plugin Composer -- visual drag-and-drop plugin builder#413ChuckBuilds wants to merge 3 commits into
Conversation
Web UI (/composer/) for building a working LEDMatrix plugin without writing Python: drop elements (text, time, date, countdown, scrolling text, bar/waveform, groups, custom config variables) onto a canvas matching the real panel's pixel grid, configure them with live preview, then generate a real plugin (manager.py + manifest.json + config_schema.json) from manager.py.j2 -- downloadable as a ZIP or installed directly. NOTE: composer_bp is not yet registered in web_interface/app.py, so this blueprint is currently inert. Split out of the original chore/dead-code- removal commit, which had accidentally bundled this in alongside unrelated dead-code deletions; app.py registration was not part of that commit either and still needs to be added before this is reachable. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
- Dropped a pointless f-string prefix (no placeholders) on the default plugin description. - Replaced two bare except:pass/continue blocks (manifest.json listing, config_schema.json parsing) with a logged warning before falling through to the same skip-this-entry behavior -- same control flow, now visible in logs instead of silent. Skipped as false positives (verified against actual usage, not fixed): - Jinja2 Environment(autoescape=False) -- this env renders manager.py.j2, a Python source-code generator, never HTML; autoescaping would corrupt generated code. Flagged by a generic XSS rule that assumes all Jinja2 environments render HTML. - "Flask route directly returning a formatted string" on _as_rgb_filter -- that's a Jinja *filter* function, not a Flask route. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
|
Warning Review limit reached
Next review available in: 59 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 6 high |
| Security | 10 high 2 critical 1 medium |
🟢 Metrics 596 complexity · 27 duplication
Metric Results Complexity 596 Duplication 27
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…stall CodeQL flagged 16 high-severity "path depends on user-provided value" alerts. Investigated each: - install_locally() (/api/install) built a filesystem path from metadata.id without validating it at that point -- it was only implicitly safe because _generate_plugin_files() validates the same field (re-extracted independently) earlier in the same request. That's a real gap: reorder or change that earlier call and it's an exploitable path traversal / arbitrary file write. Fixed by validating plugin_id directly against _PLUGIN_ID_RE at the point the path is built, matching the pattern already used correctly in validate_id() and load_plugin(). - The other 10 flagged locations (serve_font's allowlist check, validate_id, load_plugin and its downstream reads) were already guarded by an explicit check earlier in the same function -- false positives from CodeQL not modeling those as sanitizers. Also fixed 2 of the 5 "stack trace exposed" warnings that were genuine: install_locally() and load_plugin() returned raw OSError/Exception text to the client in a 500 response; now logged server-side with a generic client-facing message. The other 3 (generate_zip/install_locally/ preview_code returning str(ValueError) from _generate_plugin_files) are deliberate, human-authored validation messages, not exception internals -- left as-is. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
| manifest = {} | ||
| if manifest_path.exists(): | ||
| try: | ||
| manifest = json.loads(manifest_path.read_text()) |
| logger.warning("Failed to parse config_schema.json for %s: %s", plugin_id, e) | ||
|
|
||
| manifest = {} | ||
| if manifest_path.exists(): |
|
|
||
| if schema_path.exists(): | ||
| try: | ||
| schema = json.loads(schema_path.read_text()) |
| manifest_path = plugin_dir / 'manifest.json' | ||
| config_vars = [] | ||
|
|
||
| if schema_path.exists(): |
| state_path = plugin_dir / '_composer_state.json' | ||
| if state_path.exists(): | ||
| try: | ||
| state = json.loads(state_path.read_text()) |
| target = Path(composer_bp.plugins_dir) / plugin_id | ||
| force = bool(data.get('_force', False)) | ||
|
|
||
| if target.exists() and not force: |
|
|
||
| def _save_composer_state(target_dir: Path, payload: dict) -> None: | ||
| """Persist the raw composer payload alongside the generated plugin files.""" | ||
| (target_dir / '_composer_state.json').write_text( |
| try: | ||
| files = _generate_plugin_files(data) | ||
| except ValueError as exc: | ||
| return jsonify({'status': 'error', 'message': str(exc)}), 422 |
| try: | ||
| files = _generate_plugin_files(data) | ||
| except ValueError as exc: | ||
| return jsonify({'status': 'error', 'message': str(exc)}), 422 |
| try: | ||
| files = _generate_plugin_files(data) | ||
| except ValueError as exc: | ||
| return jsonify({'status': 'error', 'message': str(exc)}), 422 |
Summary
Split out of PR #412 (
chore/dead-code-removal), which had accidentally bundled this feature in alongside unrelated dead-code deletions.A web UI (
/composer/) for building a working LEDMatrix plugin without writing Python:manager.py+manifest.json+config_schema.json) from a code-gen template, downloadable as a ZIP or installed directly intoplugins_dir.composer_bpis not registered inweb_interface/app.py, so this blueprint is currently inert (unreachable). That registration wasn't part of the original commit either — needed before this is usable, tracking as a follow-up.Also fixes 2 Codacy findings found while reviewing this code:
except: pass/continueblocks with a logged warning before the same fallthrough behavior.Skipped as false positives (verified against actual usage):
Jinja2 Environment(autoescape=False)findings — this env rendersmanager.py.j2, a Python source-code generator, never HTML; autoescaping would corrupt generated code._as_rgb_filter— that's a Jinja filter function, not a Flask route.Test plan
python3 -m py_compile web_interface/blueprints/composer.pypassescomposer_bpinapp.pybefore merging or exposing this route🤖 Generated with Claude Code
https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ