mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 16:58:06 +00:00
fix(web): address CodeRabbit review — validation, a11y, perf, and privacy fixes
Verified each finding against current code. Fixed: - api_v3: plugin_rotation_order is now strictly validated (JSON list of strings, 400 with a descriptive message otherwise) and popped from the payload before any further handling. - display_controller: _apply_plugin_rotation_order defensively ignores a non-list value (keeps the existing rotation, logs a warning) and drops non-string entries; new logs carry the [DisplayController] prefix. Unit-tested both defensive paths. - app.py: snapshot-read handler narrowed to OSError with debug logging; flask-compress ImportError now emits one structured warning with the install remedy. - htmx-config: the response-error logger prints form FIELD NAMES only - values (API keys, passwords) never reach the console. - plugin-order-list: saved order/exclusions normalized with Array.isArray (a saved "null" previously crashed .forEach); each row gained keyboard/touch-accessible move-up/move-down buttons (HTML5 drag events don't fire on most mobile browsers) that reorder and syncInputs() immediately alongside native drag. - app-shell: window.installedPlugins setter always takes the new list (same-ID metadata/enabled updates were silently dropped); tab rebuild stays gated on ID changes. LED dot renderer reads the frame with ONE getImageData call instead of one per pixel (~9,200/frame at 192x48). - plugins_manager: togglePlugin returns its request promise resolving the API outcome; the install flow now shows the "installed and enabled" toast (with Restart Now) only after enablement succeeds, and a warning without a restart offer when it fails. - a11y: hamburger aria-label flips Open/Close with drawer state; both Advanced-section toggle buttons declare aria-controls/aria-expanded and the shared toggleSection() keeps aria-expanded in sync; move buttons have per-plugin aria-labels. - Rotation/Vegas order-list bootstraps cap their retries (~5s) and show a reload hint instead of spinning forever; Alpine app-state lookups prefer [x-data="app()"] with a generic fallback. Skipped, with reasons: - executePluginAction arg order: caller (plugin_config.html) already passes (actionId, index, pluginId) matching the signature exactly. - generateFieldHtml XSS, entity-unescape blocks, dotToNested pollution, and "app.loadInstalledPlugins" in app-shell: all inside the legacy client-side config cluster whose entry points are shadowed by plugins_manager.js / replaced by server-rendered forms (zero live callers, verified) - queued for wholesale deletion in the follow-up rather than patching dead code. - custom-feeds-helpers.js findings (3): file was deleted in a prior commit. - console.error/warn override removal and afterSwap script re-execution removal: deliberate pre-existing workarounds every partial's inline init currently depends on; reworking them safely needs isolated testing (follow-up), and the error suppression is already double-gated (insertBefore AND htmx match). - "move durations bootstrap into a bundle": inline partial-scoped init is the established pattern for HTMX partials in this codebase. Validation: all 40 web tests pass; py_compile on all touched Python; all touched templates parse; rotation-order defensive paths unit-tested. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
aec1368d63
commit
8044084280
@@ -961,21 +961,22 @@ def save_main_config():
|
||||
return jsonify({"status": "error", "message": "sync_follower_position must be left or right"}), 400
|
||||
current_config["sync"]["follower_position"] = pos_val
|
||||
|
||||
# Handle primary rotation order (JSON array of plugin ids; same
|
||||
# parse/validate pattern as vegas_plugin_order above)
|
||||
# Handle primary rotation order: must be a JSON array of plugin-id
|
||||
# strings. Reject anything else with a 400 rather than silently
|
||||
# coercing, so a buggy client can't clear or corrupt the saved order.
|
||||
if 'plugin_rotation_order' in data:
|
||||
raw_order = data.pop('plugin_rotation_order')
|
||||
try:
|
||||
if isinstance(data['plugin_rotation_order'], str):
|
||||
parsed = json.loads(data['plugin_rotation_order'])
|
||||
else:
|
||||
parsed = data['plugin_rotation_order']
|
||||
if 'display' not in current_config:
|
||||
current_config['display'] = {}
|
||||
current_config['display']['plugin_rotation_order'] = (
|
||||
list(parsed) if isinstance(parsed, (list, tuple)) else []
|
||||
)
|
||||
parsed = json.loads(raw_order) if isinstance(raw_order, str) else raw_order
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
logger.warning("Malformed plugin_rotation_order ignored")
|
||||
return jsonify({'status': 'error',
|
||||
'message': 'plugin_rotation_order must be valid JSON'}), 400
|
||||
if not isinstance(parsed, list) or not all(isinstance(p, str) for p in parsed):
|
||||
return jsonify({'status': 'error',
|
||||
'message': 'plugin_rotation_order must be a list of plugin-id strings'}), 400
|
||||
if 'display' not in current_config:
|
||||
current_config['display'] = {}
|
||||
current_config['display']['plugin_rotation_order'] = parsed
|
||||
|
||||
# Handle display durations
|
||||
duration_fields = [k for k in data.keys() if k.endswith('_duration') or k in ['default_duration', 'transition_duration']]
|
||||
|
||||
Reference in New Issue
Block a user