mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
fix: web UI and src.common bugs (wifi wrong-password, plugin icon, starlark toggle, API caching, scroll/logo/font helpers) (#646)
- wifi: keep the "wrong_password:" prefix through the restore/AP fallback so the UI's incorrect-password prompt fires again. - /plugins/installed returns the manifest's icon (string only). - /starlark/apps/<id>/toggle coerces `enabled` and delegates to _toggle_starlark_app (disk before memory, no KeyError, "false" is false). - /api/v3/ JSON GETs are sent Cache-Control: no-store; non-JSON keeps 5s. - ScrollHelper.set_scrolling_image converts non-RGB input (alpha onto black); create/set_scrolling_image reset last_update_time like reset_scroll. - LogoHelper backs off a failed download per path for MISSING_LOGO_RECHECK_SECONDS; cleared on invalidate/clear_cache. - refresh_placeholder_timestamp saves atomically. - FontManager.clear_cache / _clear_plugin_font_cache bump cache_generation. - Odds manager: per-game logs to DEBUG; JSON decode error caught before RequestException (same cooldown). - element_style mangled continuations; startup validator skips null plugin blocks and reuses the controller's discovery. - src/common/README lists frame_timing, json_body, render_gate. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -570,10 +570,16 @@ def add_security_headers(response):
|
||||
response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
|
||||
response.headers['Expires'] = (datetime.now() + timedelta(days=365)).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
elif request.path.startswith('/api/v3/'):
|
||||
# Short cache for API responses (5 seconds) to allow for quick updates
|
||||
# but reduce server load for repeated requests
|
||||
if request.method == 'GET' and 'stream' not in request.path:
|
||||
response.headers['Cache-Control'] = 'private, max-age=5, must-revalidate'
|
||||
if response.mimetype == 'application/json':
|
||||
# JSON is live state. A cached copy made a fetch() right after
|
||||
# an install, toggle or Wi-Fi connect show the state from
|
||||
# before it for up to the cache lifetime.
|
||||
response.headers['Cache-Control'] = 'no-store'
|
||||
else:
|
||||
# Files served through the API (plugin web UI assets, backup
|
||||
# downloads) keep the short cache.
|
||||
response.headers['Cache-Control'] = 'private, max-age=5, must-revalidate'
|
||||
else:
|
||||
# No cache for HTML pages to ensure fresh content
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
|
||||
@@ -153,6 +153,9 @@ def get_installed_plugins():
|
||||
'category': plugin_info.get('category', 'General'),
|
||||
'description': plugin_info.get('description', 'No description available'),
|
||||
'tags': plugin_info.get('tags', []),
|
||||
# The tab nav uses this as the <i> element's Font Awesome class
|
||||
# (app-shell.js / app-early.js); only a string can be one.
|
||||
'icon': plugin_info.get('icon') if isinstance(plugin_info.get('icon'), str) else None,
|
||||
'enabled': enabled,
|
||||
'verified': verified,
|
||||
'loaded': plugin_info.get('loaded', False),
|
||||
|
||||
@@ -21,6 +21,7 @@ from web_interface.blueprints.api_v3 import (
|
||||
subprocess, tempfile,
|
||||
)
|
||||
import web_interface.blueprints.api_v3 as _pkg
|
||||
from web_interface.blueprints.api_v3.wifi import _parse_bool_ish
|
||||
# Read through the module rather than bound by value: tests patch these
|
||||
# as module attributes, and a value binding would not see the patch.
|
||||
# Several are also called from helpers that live in __init__, so the
|
||||
@@ -547,36 +548,33 @@ def toggle_starlark_app(app_id):
|
||||
try:
|
||||
data = request.get_json(silent=True) or {}
|
||||
|
||||
starlark_plugin = _pkg._get_starlark_plugin()
|
||||
if starlark_plugin:
|
||||
app = starlark_plugin.apps.get(app_id)
|
||||
if not app:
|
||||
return jsonify({'status': 'error', 'message': f'App not found: {app_id}'}), 404
|
||||
enabled = data.get('enabled')
|
||||
if enabled is None:
|
||||
# The write itself goes through _toggle_starlark_app, the same helper
|
||||
# /plugins/toggle uses: it saves to disk before touching the loaded
|
||||
# app, and tolerates a loaded app with no on-disk entry. This route
|
||||
# used to carry its own copy without either fix, and stored "false"
|
||||
# (a truthy string) as-is.
|
||||
enabled = data.get('enabled')
|
||||
if enabled is None:
|
||||
_, err = _validate_starlark_app_path(app_id)
|
||||
if err:
|
||||
return jsonify({'status': 'error', 'message': err}), 400
|
||||
# No explicit state: flip the current one.
|
||||
starlark_plugin = _pkg._get_starlark_plugin()
|
||||
app = starlark_plugin.apps.get(app_id) if starlark_plugin else None
|
||||
if app is not None:
|
||||
enabled = not app.is_enabled()
|
||||
app.manifest['enabled'] = enabled
|
||||
# Use safe manifest update to prevent race conditions
|
||||
def update_fn(manifest):
|
||||
manifest['apps'][app_id]['enabled'] = enabled
|
||||
starlark_plugin._update_manifest_safe(update_fn)
|
||||
return jsonify({'status': 'success', 'message': f"App {'enabled' if enabled else 'disabled'}", 'enabled': enabled})
|
||||
|
||||
# Standalone: update manifest directly
|
||||
with _starlark_manifest_lock():
|
||||
manifest = _pkg._read_starlark_manifest()
|
||||
app_data = manifest.get('apps', {}).get(app_id)
|
||||
if not app_data:
|
||||
return jsonify({'status': 'error', 'message': f'App not found: {app_id}'}), 404
|
||||
|
||||
enabled = data.get('enabled')
|
||||
if enabled is None:
|
||||
enabled = not app_data.get('enabled', True)
|
||||
app_data['enabled'] = enabled
|
||||
if _pkg._write_starlark_manifest(manifest):
|
||||
return jsonify({'status': 'success', 'message': f"App {'enabled' if enabled else 'disabled'}", 'enabled': enabled})
|
||||
else:
|
||||
return jsonify({'status': 'error', 'message': 'Failed to save'}), 500
|
||||
app_data = _pkg._read_starlark_manifest().get('apps', {}).get(app_id)
|
||||
if not app_data:
|
||||
return jsonify({'status': 'error', 'message': f'App not found: {app_id}'}), 404
|
||||
enabled = not app_data.get('enabled', True)
|
||||
else:
|
||||
enabled = _parse_bool_ish(enabled)
|
||||
if enabled is None:
|
||||
return jsonify({'status': 'error',
|
||||
'message': 'enabled must be true or false'}), 400
|
||||
|
||||
return _pkg._toggle_starlark_app(app_id, enabled)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("[Starlark] toggle_starlark_app failed")
|
||||
|
||||
Reference in New Issue
Block a user