mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 06:45:09 +00:00
fix(web): plugin dir resolver in routes, nmcli AP detection, daemon config reload, upload safety, BDF preview (#655)
* fix(web): plugin dir resolver in routes, nmcli AP detection, daemon config reload, upload safety - Route plugin lookups (installed list, update, recorded version, config form, web UI pages) through the plugin manager's resolver so plugins in ledmatrix-<id> directories work. - Captive-portal detection also sees the nmcli fallback AP (cached). - WiFi monitor daemon re-reads wifi_config.json when its mtime changes. - Drop the AP check in disconnect_from_network that could never fire. - LED status file per WiFiManager; config path falls back to this checkout. - BDF font preview via src.common.bdf_font. - Asset uploads validate every file before saving; metadata and calendar credentials written atomically; no absolute path in the response; asset delete answers 400 for a missing body. - Coerce string booleans in plugin toggle, on-demand start and AP force. - SSE broadcaster clears its thread handle before exiting. - start.py log filter handles every exc_info form. - Cleanups: unused plugins/fonts partial work, duplicate backup catch-alls, raw-config error helper, update-route tidy, redundant imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): request BDF font previews now that the server renders them The Fonts tab skipped the preview request for .bdf files because the server used to refuse them; /fonts/preview now draws BDF with the shared loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): take the update route's plugin directory from a directory listing CodeQL flagged the path built from the request's plugin_id (the id was already validated with safe_path_component, which CodeQL doesn't model; the same flow on main is alerts 738/739). The directory is now the entry of plugins_dir matched by name, so nothing built from user input reaches the filesystem; an id with nothing installed goes to the store manager, which reports it not found as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): read the blueprint's plugin_manager defensively in _plugin_directory _get_plugin_version now goes through _plugin_directory, which read api_v3.plugin_manager directly; the attribute exists only once the app sets it, so test_path_traversal_guards::test_a_real_manifest_is_read failed when run on its own (order-dependent in the full suite). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -309,13 +309,30 @@ def get_font_preview() -> tuple[Response, int] | Response:
|
||||
|
||||
# Load font
|
||||
font = None
|
||||
if str(font_path).endswith('.bdf'):
|
||||
# BDF fonts require complex per-glyph rendering via freetype
|
||||
# Return explicit error rather than showing misleading preview with default font
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'BDF font preview not supported. BDF fonts will render correctly on the LED matrix.'
|
||||
}), 400
|
||||
bdf_face = None
|
||||
lines = text.split('\n')
|
||||
line_height = 0
|
||||
if font_path.suffix.lower() == '.bdf':
|
||||
# PIL cannot draw a BDF strike, so it goes through the same loader
|
||||
# and glyph rasterizer the panel uses (src/common/bdf_font.py). The
|
||||
# face comes back at the file's native size when it has no strike at
|
||||
# the one asked for -- which is also what the panel would draw.
|
||||
from src.common.bdf_font import load_bdf_face, draw_bdf_text
|
||||
try:
|
||||
bdf_face, realised_px = load_bdf_face(str(font_path), size)
|
||||
except Exception as e:
|
||||
logger.warning("[FontPreview] Failed to load BDF font %s: %s", font_path, e)
|
||||
return jsonify({'status': 'error', 'message': 'Could not load BDF font'}), 400
|
||||
line_height = (bdf_face.size.height >> 6) or realised_px
|
||||
line_widths = []
|
||||
for line in lines:
|
||||
width = 0
|
||||
for char in line:
|
||||
bdf_face.load_char(char)
|
||||
width += bdf_face.glyph.advance.x >> 6
|
||||
line_widths.append(width)
|
||||
text_width = max(line_widths)
|
||||
text_height = line_height * len(lines)
|
||||
else:
|
||||
# TTF/OTF fonts
|
||||
try:
|
||||
@@ -325,12 +342,12 @@ def get_font_preview() -> tuple[Response, int] | Response:
|
||||
logger.warning("[FontPreview] Failed to load font %s: %s", font_path, e)
|
||||
font = ImageFont.load_default()
|
||||
|
||||
# Calculate text size
|
||||
temp_img = Image.new('RGB', (1, 1))
|
||||
temp_draw = ImageDraw.Draw(temp_img)
|
||||
bbox = temp_draw.textbbox((0, 0), text, font=font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
text_height = bbox[3] - bbox[1]
|
||||
# Calculate text size
|
||||
temp_img = Image.new('RGB', (1, 1))
|
||||
temp_draw = ImageDraw.Draw(temp_img)
|
||||
bbox = temp_draw.textbbox((0, 0), text, font=font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
text_height = bbox[3] - bbox[1]
|
||||
|
||||
# Create image with padding
|
||||
padding = 10
|
||||
@@ -350,7 +367,11 @@ def get_font_preview() -> tuple[Response, int] | Response:
|
||||
x = (img_width - text_width) // 2
|
||||
y = (img_height - text_height) // 2
|
||||
|
||||
draw.text((x, y), text, font=font, fill=fg_rgb)
|
||||
if bdf_face is not None:
|
||||
for index, line in enumerate(lines):
|
||||
draw_bdf_text(draw, line, x, y + index * line_height, bdf_face, color=fg_rgb)
|
||||
else:
|
||||
draw.text((x, y), text, font=font, fill=fg_rgb)
|
||||
|
||||
# Convert to base64
|
||||
buffer = io.BytesIO()
|
||||
|
||||
Reference in New Issue
Block a user