mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
fix(config): Add defaults to schemas and fix config validation issues (#153)
* fix(web): Resolve font display and config API error handling issues - Fix font catalog display error where path.startsWith fails (path is object, not string) - Update save_main_config to use error_response() helper - Improve save_raw_main_config error handling consistency - Add proper error codes and traceback details to API responses * fix(web): Prevent fontCatalog redeclaration error on HTMX reload - Use window object to store global font variables - Check if script has already loaded before declaring variables - Update both window properties and local references on assignment - Fixes 'Identifier fontCatalog has already been declared' error * fix(web): Wrap fonts script in IIFE to prevent all redeclaration errors - Wrap entire script in IIFE that only runs once - Check if script already loaded before declaring variables/functions - Expose initializeFontsTab to window for re-initialization - Prevents 'Identifier has already been declared' errors on HTMX reload * fix(web): Exempt config save API endpoints from CSRF protection - Exempt save_raw_main_config, save_raw_secrets_config, and save_main_config from CSRF - These endpoints are called via fetch from JavaScript and don't include CSRF tokens - Fixes 500 error when saving config via raw JSON editor * fix(web): Exempt system action endpoint from CSRF protection - Exempt execute_system_action from CSRF - Fixes 500 error when using system action buttons (restart display, restart Pi, etc.) - These endpoints are called via HTMX and don't include CSRF tokens * fix(web): Exempt all API v3 endpoints from CSRF protection - Add before_request handler to exempt all api_v3.* endpoints - All API endpoints are programmatic (HTMX/fetch) and don't include CSRF tokens - Prevents future CSRF errors on any API endpoint - Cleaner than exempting individual endpoints * refactor(web): Remove CSRF protection for local-only application - CSRF is designed for internet-facing apps to prevent cross-site attacks - For local-only Raspberry Pi app, threat model is different - All endpoints were exempted anyway, so it wasn't protecting anything - Forms use HTMX without CSRF tokens - If exposing to internet later, can re-enable with proper token implementation * fix(web): Fix font path double-prefixing in font catalog display - Only prefix with 'assets/fonts/' if path is a bare filename - If path starts with '/' (absolute) or 'assets/' (already prefixed), use as-is - Fixes double-prefixing when get_fonts_catalog returns relative paths like 'assets/fonts/press_start.ttf' * fix(web): Remove fontsTabInitialized guard to allow re-initialization on HTMX reload - Remove fontsTabInitialized check that prevented re-initialization on HTMX content swap - The window._fontsScriptLoaded guard is sufficient to prevent function redeclaration - Allow initializeFontsTab() to run on each HTMX swap to attach listeners to new DOM elements - Fixes fonts UI breaking after HTMX reload (buttons, upload dropzone, etc. not working) * fix(api): Preserve empty strings for optional string fields in plugin config - Add _is_field_required() helper to check if fields are required in schema - Update _parse_form_value_with_schema() to preserve empty strings for optional string fields - Fixes 400 error when saving MQTT plugin config with empty username/password - Resolves validation error: 'Expected type string, got NoneType' * fix(config): Add defaults to schemas and fix None value handling - Updated merge_with_defaults to replace None values with defaults - Fixed form processing to skip empty optional fields without defaults - Added script to automatically add defaults to all plugin config schemas - Added defaults to 89 fields across 10 plugin schemas - Prevents validation errors from None values in configs Changes: - schema_manager.py: Enhanced merge_with_defaults to replace None with defaults - api_v3.py: Added _SKIP_FIELD sentinel to skip optional fields without defaults - add_defaults_to_schemas.py: Script to add sensible defaults to schemas - Plugin schemas: Added defaults for number, boolean, and array fields * fix(config): Fix save button spinner by checking HTTP status code - Fixed handleConfigSave to check xhr.status instead of event.detail.successful - With hx-swap="none", HTMX doesn't set event.detail.successful - Now properly detects successful saves (status 200-299) and stops spinner - Improved error message extraction from API responses - Also fixed handleToggleResponse for consistency --------- Co-authored-by: Chuck <chuck@example.com>
This commit is contained in:
@@ -4209,13 +4209,28 @@
|
||||
const btn = event.target.querySelector('[type=submit]');
|
||||
if (btn) btn.disabled = false;
|
||||
|
||||
if (event.detail.successful) {
|
||||
showNotification('Configuration saved successfully!', 'success');
|
||||
const xhr = event.detail.xhr;
|
||||
const status = xhr?.status || 0;
|
||||
|
||||
// Check if request was successful (2xx status codes)
|
||||
if (status >= 200 && status < 300) {
|
||||
// Try to get message from response JSON
|
||||
let message = 'Configuration saved successfully!';
|
||||
try {
|
||||
if (xhr?.responseJSON?.message) {
|
||||
message = xhr.responseJSON.message;
|
||||
} else if (xhr?.responseText) {
|
||||
const responseData = JSON.parse(xhr.responseText);
|
||||
message = responseData.message || message;
|
||||
}
|
||||
} catch (e) {
|
||||
// Use default message if parsing fails
|
||||
}
|
||||
showNotification(message, 'success');
|
||||
} else {
|
||||
// Log detailed error information
|
||||
const xhr = event.detail.xhr;
|
||||
// Request failed - log detailed error information
|
||||
console.error('Config save failed:', {
|
||||
status: xhr?.status,
|
||||
status: status,
|
||||
statusText: xhr?.statusText,
|
||||
responseText: xhr?.responseText
|
||||
});
|
||||
@@ -4223,7 +4238,13 @@
|
||||
// Try to parse error response
|
||||
let errorMessage = 'Failed to save configuration';
|
||||
try {
|
||||
if (xhr?.responseText) {
|
||||
if (xhr?.responseJSON) {
|
||||
const errorData = xhr.responseJSON;
|
||||
errorMessage = errorData.message || errorData.details || errorMessage;
|
||||
if (errorData.validation_errors) {
|
||||
errorMessage += ': ' + errorData.validation_errors.join(', ');
|
||||
}
|
||||
} else if (xhr?.responseText) {
|
||||
const errorData = JSON.parse(xhr.responseText);
|
||||
errorMessage = errorData.message || errorData.details || errorMessage;
|
||||
if (errorData.validation_errors) {
|
||||
@@ -4241,7 +4262,10 @@
|
||||
|
||||
// Handle toggle response
|
||||
window.handleToggleResponse = function(event, pluginId) {
|
||||
if (event.detail.successful) {
|
||||
const xhr = event.detail.xhr;
|
||||
const status = xhr?.status || 0;
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
// Update UI in place instead of refreshing to avoid duplication
|
||||
const checkbox = document.getElementById(`plugin-enabled-${pluginId}`);
|
||||
const label = checkbox?.nextElementSibling;
|
||||
@@ -4252,14 +4276,39 @@
|
||||
label.className = `ml-2 text-sm ${isEnabled ? 'text-green-600' : 'text-gray-500'}`;
|
||||
}
|
||||
|
||||
showNotification('Plugin status updated', 'success');
|
||||
// Try to get message from response
|
||||
let message = 'Plugin status updated';
|
||||
try {
|
||||
if (xhr?.responseJSON?.message) {
|
||||
message = xhr.responseJSON.message;
|
||||
} else if (xhr?.responseText) {
|
||||
const responseData = JSON.parse(xhr.responseText);
|
||||
message = responseData.message || message;
|
||||
}
|
||||
} catch (e) {
|
||||
// Use default message
|
||||
}
|
||||
showNotification(message, 'success');
|
||||
} else {
|
||||
// Revert checkbox state on error
|
||||
const checkbox = document.getElementById(`plugin-enabled-${pluginId}`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
}
|
||||
showNotification('Failed to update plugin status', 'error');
|
||||
|
||||
// Try to get error message from response
|
||||
let errorMessage = 'Failed to update plugin status';
|
||||
try {
|
||||
if (xhr?.responseJSON?.message) {
|
||||
errorMessage = xhr.responseJSON.message;
|
||||
} else if (xhr?.responseText) {
|
||||
const errorData = JSON.parse(xhr.responseText);
|
||||
errorMessage = errorData.message || errorData.details || errorMessage;
|
||||
}
|
||||
} catch (e) {
|
||||
// Use default message
|
||||
}
|
||||
showNotification(errorMessage, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user