diff --git a/web_interface/blueprints/api_v3.py b/web_interface/blueprints/api_v3.py index 92a296f2..5c783fa4 100644 --- a/web_interface/blueprints/api_v3.py +++ b/web_interface/blueprints/api_v3.py @@ -740,6 +740,39 @@ def save_main_config(): except (ValueError, TypeError): return jsonify({'status': 'error', 'message': f"Invalid multiplexing value '{data['multiplexing']}'. Must be an integer from 0 to 22."}), 400 + # Validate integer display hardware fields (bounds check) + _int_field_limits = { + 'rows': (8, 128), + 'cols': (16, 128), + 'chain_length': (1, 32), + 'parallel': (1, 4), + 'brightness': (1, 100), + 'scan_mode': (0, 1), + 'pwm_bits': (1, 11), + 'pwm_dither_bits': (0, 2), + 'pwm_lsb_nanoseconds': (50, 500), + 'limit_refresh_rate_hz': (0, 1000), + 'gpio_slowdown': (0, 5), + 'max_dynamic_duration_seconds': (1, 3600), + } + for field, (lo, hi) in _int_field_limits.items(): + if field in data: + raw = data[field] + if isinstance(raw, bool): + return jsonify({'status': 'error', 'message': f"Invalid {field} value '{raw}'. Must be an integer."}), 400 + if isinstance(raw, float): + return jsonify({'status': 'error', 'message': f"Invalid {field} value '{raw}'. Must be an integer, not a float."}), 400 + if isinstance(raw, int): + val = raw + elif isinstance(raw, str): + if not re.fullmatch(r'-?\d+', raw): + return jsonify({'status': 'error', 'message': f"Invalid {field} value '{raw}'. Must be an integer."}), 400 + val = int(raw) + else: + return jsonify({'status': 'error', 'message': f"Invalid {field} value '{raw}'. Must be an integer."}), 400 + if val < lo or val > hi: + return jsonify({'status': 'error', 'message': f"Invalid {field} value {val}. Must be between {lo} and {hi}."}), 400 + # Handle hardware settings for field in ['rows', 'cols', 'chain_length', 'parallel', 'brightness', 'hardware_mapping', 'scan_mode', 'pwm_bits', 'pwm_dither_bits', 'pwm_lsb_nanoseconds', 'limit_refresh_rate_hz', @@ -767,7 +800,7 @@ def save_main_config(): if 'max_dynamic_duration_seconds' in data: if 'dynamic_duration' not in current_config['display']: current_config['display']['dynamic_duration'] = {} - current_config['display']['dynamic_duration']['max_duration_seconds'] = int(data['max_dynamic_duration_seconds']) + current_config['display']['dynamic_duration']['max_duration_seconds'] = int(data['max_dynamic_duration_seconds']) # Already validated by _int_field_limits # Handle Vegas scroll mode settings vegas_fields = ['vegas_scroll_enabled', 'vegas_scroll_speed', 'vegas_separator_width', diff --git a/web_interface/templates/v3/partials/display.html b/web_interface/templates/v3/partials/display.html index aeebd915..b059f64f 100644 --- a/web_interface/templates/v3/partials/display.html +++ b/web_interface/templates/v3/partials/display.html @@ -49,9 +49,9 @@ name="chain_length" value="{{ main_config.display.hardware.chain_length or 2 }}" min="1" - max="8" + max="32" class="form-control"> -
Number of LED panels chained together
+Number of LED panels chained together (e.g. 2 for 128×32, 5 for 320×32)