mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +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:
|
except Exception as e:
|
||||||
return False, str(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():
|
def _get_display_service_status():
|
||||||
"""Return status information about the ledmatrix service."""
|
"""Return status information about the ledmatrix service."""
|
||||||
try:
|
try:
|
||||||
@@ -612,7 +635,7 @@ def save_main_config():
|
|||||||
|
|
||||||
if is_general_update:
|
if is_general_update:
|
||||||
# For checkbox: if not present in data during general update, it means unchecked
|
# 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:
|
if 'timezone' in data:
|
||||||
current_config['timezone'] = data['timezone']
|
current_config['timezone'] = data['timezone']
|
||||||
@@ -633,10 +656,10 @@ def save_main_config():
|
|||||||
if 'plugin_system' not in current_config:
|
if 'plugin_system' not in current_config:
|
||||||
current_config['plugin_system'] = {}
|
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']:
|
for checkbox in ['auto_discover', 'auto_load_enabled', 'development_mode']:
|
||||||
if checkbox in data:
|
current_config['plugin_system'][checkbox] = _coerce_to_bool(data.get(checkbox))
|
||||||
current_config['plugin_system'][checkbox] = data.get(checkbox, False)
|
|
||||||
|
|
||||||
# Handle plugins_directory
|
# Handle plugins_directory
|
||||||
if 'plugins_directory' in data:
|
if 'plugins_directory' in data:
|
||||||
@@ -670,13 +693,12 @@ def save_main_config():
|
|||||||
if 'gpio_slowdown' in data:
|
if 'gpio_slowdown' in data:
|
||||||
current_config['display']['runtime']['gpio_slowdown'] = int(data['gpio_slowdown'])
|
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']:
|
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
|
# Handle display-level checkboxes (always set to handle unchecked state)
|
||||||
if 'use_short_date_format' in data:
|
current_config['display']['use_short_date_format'] = _coerce_to_bool(data.get('use_short_date_format'))
|
||||||
current_config['display']['use_short_date_format'] = data.get('use_short_date_format', False)
|
|
||||||
|
|
||||||
# Handle dynamic duration settings
|
# Handle dynamic duration settings
|
||||||
if 'max_dynamic_duration_seconds' in data:
|
if 'max_dynamic_duration_seconds' in data:
|
||||||
@@ -696,14 +718,11 @@ def save_main_config():
|
|||||||
|
|
||||||
vegas_config = current_config['display']['vegas_scroll']
|
vegas_config = current_config['display']['vegas_scroll']
|
||||||
|
|
||||||
# Ensure a default enabled value exists on first init
|
# Handle enabled checkbox
|
||||||
vegas_config.setdefault('enabled', True)
|
# HTML checkboxes omit the key entirely when unchecked, so if the form
|
||||||
|
# was submitted (any vegas field present) but enabled key is missing,
|
||||||
# Handle enabled checkbox only when explicitly provided
|
# the checkbox was unchecked and we should set enabled=False
|
||||||
# (HTML checkbox sends "on" string when checked, omits key when unchecked)
|
vegas_config['enabled'] = _coerce_to_bool(data.get('vegas_scroll_enabled'))
|
||||||
if 'vegas_scroll_enabled' in data:
|
|
||||||
enabled_value = data['vegas_scroll_enabled']
|
|
||||||
vegas_config['enabled'] = enabled_value in (True, 'on', 'true', '1', 1)
|
|
||||||
|
|
||||||
# Handle numeric settings with validation
|
# Handle numeric settings with validation
|
||||||
numeric_fields = {
|
numeric_fields = {
|
||||||
|
|||||||
@@ -182,6 +182,7 @@
|
|||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="disable_hardware_pulsing"
|
name="disable_hardware_pulsing"
|
||||||
|
value="true"
|
||||||
{% if main_config.display.hardware.disable_hardware_pulsing %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Disable Hardware Pulsing</span>
|
||||||
@@ -192,6 +193,7 @@
|
|||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="inverse_colors"
|
name="inverse_colors"
|
||||||
|
value="true"
|
||||||
{% if main_config.display.hardware.inverse_colors %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Inverse Colors</span>
|
||||||
@@ -202,6 +204,7 @@
|
|||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="show_refresh_rate"
|
name="show_refresh_rate"
|
||||||
|
value="true"
|
||||||
{% if main_config.display.hardware.show_refresh_rate %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Show Refresh Rate</span>
|
||||||
@@ -212,6 +215,7 @@
|
|||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="use_short_date_format"
|
name="use_short_date_format"
|
||||||
|
value="true"
|
||||||
{% if main_config.display.use_short_date_format %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Use Short Date Format</span>
|
||||||
@@ -251,6 +255,7 @@
|
|||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
id="vegas_scroll_enabled"
|
id="vegas_scroll_enabled"
|
||||||
name="vegas_scroll_enabled"
|
name="vegas_scroll_enabled"
|
||||||
|
value="true"
|
||||||
aria-label="Enable Vegas Scroll Mode"
|
aria-label="Enable Vegas Scroll Mode"
|
||||||
{% if main_config.display.get('vegas_scroll', {}).get('enabled', false) %}checked{% endif %}
|
{% 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">
|
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">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="web_display_autostart"
|
name="web_display_autostart"
|
||||||
|
value="true"
|
||||||
{% if main_config.web_display_autostart %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Web Display Autostart</span>
|
||||||
@@ -118,6 +119,7 @@
|
|||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="auto_discover"
|
name="auto_discover"
|
||||||
|
value="true"
|
||||||
{% if main_config.get('plugin_system', {}).get('auto_discover', True) %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Auto Discover Plugins</span>
|
||||||
@@ -130,6 +132,7 @@
|
|||||||
<label class="flex items-center">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="auto_load_enabled"
|
name="auto_load_enabled"
|
||||||
|
value="true"
|
||||||
{% if main_config.get('plugin_system', {}).get('auto_load_enabled', True) %}checked{% endif %}
|
{% 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">
|
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>
|
<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">
|
<label class="flex items-center">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="development_mode"
|
name="development_mode"
|
||||||
|
value="true"
|
||||||
{% if main_config.get('plugin_system', {}).get('development_mode', False) %}checked{% endif %}
|
{% 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">
|
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>
|
<span class="ml-2 text-sm font-medium text-gray-900">Development Mode</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user