mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
fix(web): ensure unchecked checkboxes save as false in main config forms (#222)
* fix: remove plugin-specific calendar duration from config template
Plugin display durations should be added dynamically when plugins are
installed, not hardcoded in the template.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(web): ensure unchecked checkboxes save as false in main config forms
HTML checkboxes omit their key entirely when unchecked, so the backend
never received updates to set boolean values to false. This affected:
- vegas_scroll_enabled: Now uses _coerce_to_bool helper
- use_short_date_format: Now uses _coerce_to_bool helper
- Plugin system checkboxes (auto_discover, auto_load_enabled, development_mode):
Now uses _coerce_to_bool helper
- Hardware checkboxes (disable_hardware_pulsing, inverse_colors, show_refresh_rate):
Now uses _coerce_to_bool helper
- web_display_autostart: Now uses _coerce_to_bool helper
Added _coerce_to_bool() helper function that properly converts form string
values ("true", "on", "1", "yes") to actual Python booleans, ensuring
consistent JSON types in config and correct downstream boolean checks.
Also added value="true" to all main config checkboxes for consistent boolean
parsing (sends "true" instead of "on" when checked).
This is the same issue fixed in commit 10d70d91 for plugin configs, but
for the main configuration forms (display settings, general settings).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Chuck <chuck@example.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,29 @@ def _save_config_atomic(config_manager, config_data, create_backup=True):
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def _coerce_to_bool(value):
|
||||
"""
|
||||
Coerce a form value to a proper Python boolean.
|
||||
|
||||
HTML checkboxes send string values like "true", "on", "1" when checked.
|
||||
This ensures we store actual booleans in config JSON, not strings.
|
||||
|
||||
Args:
|
||||
value: The form value (string, bool, int, or None)
|
||||
|
||||
Returns:
|
||||
bool: True if value represents a truthy checkbox state, False otherwise
|
||||
"""
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, int):
|
||||
return value == 1
|
||||
if isinstance(value, str):
|
||||
return value.lower() in ('true', 'on', '1', 'yes')
|
||||
return False
|
||||
|
||||
def _get_display_service_status():
|
||||
"""Return status information about the ledmatrix service."""
|
||||
try:
|
||||
@@ -612,7 +635,7 @@ def save_main_config():
|
||||
|
||||
if is_general_update:
|
||||
# For checkbox: if not present in data during general update, it means unchecked
|
||||
current_config['web_display_autostart'] = data.get('web_display_autostart', False)
|
||||
current_config['web_display_autostart'] = _coerce_to_bool(data.get('web_display_autostart'))
|
||||
|
||||
if 'timezone' in data:
|
||||
current_config['timezone'] = data['timezone']
|
||||
@@ -633,10 +656,10 @@ def save_main_config():
|
||||
if 'plugin_system' not in current_config:
|
||||
current_config['plugin_system'] = {}
|
||||
|
||||
# Handle plugin system checkboxes
|
||||
# Handle plugin system checkboxes - always set to handle unchecked state
|
||||
# HTML checkboxes omit the key when unchecked, so missing key = unchecked = False
|
||||
for checkbox in ['auto_discover', 'auto_load_enabled', 'development_mode']:
|
||||
if checkbox in data:
|
||||
current_config['plugin_system'][checkbox] = data.get(checkbox, False)
|
||||
current_config['plugin_system'][checkbox] = _coerce_to_bool(data.get(checkbox))
|
||||
|
||||
# Handle plugins_directory
|
||||
if 'plugins_directory' in data:
|
||||
@@ -670,13 +693,12 @@ def save_main_config():
|
||||
if 'gpio_slowdown' in data:
|
||||
current_config['display']['runtime']['gpio_slowdown'] = int(data['gpio_slowdown'])
|
||||
|
||||
# Handle checkboxes
|
||||
# Handle checkboxes - coerce to bool to ensure proper JSON types
|
||||
for checkbox in ['disable_hardware_pulsing', 'inverse_colors', 'show_refresh_rate']:
|
||||
current_config['display']['hardware'][checkbox] = data.get(checkbox, False)
|
||||
current_config['display']['hardware'][checkbox] = _coerce_to_bool(data.get(checkbox))
|
||||
|
||||
# Handle display-level checkboxes
|
||||
if 'use_short_date_format' in data:
|
||||
current_config['display']['use_short_date_format'] = data.get('use_short_date_format', False)
|
||||
# Handle display-level checkboxes (always set to handle unchecked state)
|
||||
current_config['display']['use_short_date_format'] = _coerce_to_bool(data.get('use_short_date_format'))
|
||||
|
||||
# Handle dynamic duration settings
|
||||
if 'max_dynamic_duration_seconds' in data:
|
||||
@@ -696,14 +718,11 @@ def save_main_config():
|
||||
|
||||
vegas_config = current_config['display']['vegas_scroll']
|
||||
|
||||
# Ensure a default enabled value exists on first init
|
||||
vegas_config.setdefault('enabled', True)
|
||||
|
||||
# Handle enabled checkbox only when explicitly provided
|
||||
# (HTML checkbox sends "on" string when checked, omits key when unchecked)
|
||||
if 'vegas_scroll_enabled' in data:
|
||||
enabled_value = data['vegas_scroll_enabled']
|
||||
vegas_config['enabled'] = enabled_value in (True, 'on', 'true', '1', 1)
|
||||
# Handle enabled checkbox
|
||||
# HTML checkboxes omit the key entirely when unchecked, so if the form
|
||||
# was submitted (any vegas field present) but enabled key is missing,
|
||||
# the checkbox was unchecked and we should set enabled=False
|
||||
vegas_config['enabled'] = _coerce_to_bool(data.get('vegas_scroll_enabled'))
|
||||
|
||||
# Handle numeric settings with validation
|
||||
numeric_fields = {
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="disable_hardware_pulsing"
|
||||
value="true"
|
||||
{% if main_config.display.hardware.disable_hardware_pulsing %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Disable Hardware Pulsing</span>
|
||||
@@ -192,6 +193,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="inverse_colors"
|
||||
value="true"
|
||||
{% if main_config.display.hardware.inverse_colors %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Inverse Colors</span>
|
||||
@@ -202,6 +204,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="show_refresh_rate"
|
||||
value="true"
|
||||
{% if main_config.display.hardware.show_refresh_rate %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Show Refresh Rate</span>
|
||||
@@ -212,6 +215,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="use_short_date_format"
|
||||
value="true"
|
||||
{% if main_config.display.use_short_date_format %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Use Short Date Format</span>
|
||||
@@ -251,6 +255,7 @@
|
||||
<input type="checkbox"
|
||||
id="vegas_scroll_enabled"
|
||||
name="vegas_scroll_enabled"
|
||||
value="true"
|
||||
aria-label="Enable Vegas Scroll Mode"
|
||||
{% if main_config.display.get('vegas_scroll', {}).get('enabled', false) %}checked{% endif %}
|
||||
class="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="web_display_autostart"
|
||||
value="true"
|
||||
{% if main_config.web_display_autostart %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Web Display Autostart</span>
|
||||
@@ -118,6 +119,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="auto_discover"
|
||||
value="true"
|
||||
{% if main_config.get('plugin_system', {}).get('auto_discover', True) %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Auto Discover Plugins</span>
|
||||
@@ -130,6 +132,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="auto_load_enabled"
|
||||
value="true"
|
||||
{% if main_config.get('plugin_system', {}).get('auto_load_enabled', True) %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Auto Load Enabled Plugins</span>
|
||||
@@ -142,6 +145,7 @@
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="development_mode"
|
||||
value="true"
|
||||
{% if main_config.get('plugin_system', {}).get('development_mode', False) %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Development Mode</span>
|
||||
|
||||
Reference in New Issue
Block a user