mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-11 05:13:01 +00:00
fix(web-ui): Fix file upload widget and plugin action buttons (#165)
* fix(plugins): Resolve plugin ID determination error in action buttons
- Fix server-side template parameter order for executePluginAction
- Add data-plugin-id attributes to action buttons in all templates
- Enhance executePluginAction with comprehensive fallback logic
- Support retrieving pluginId from DOM, Alpine context, and config state
- Fixes 'Unable to determine plugin ID' error for Spotify/YouTube auth
* fix(plugins): Add missing button IDs and status divs in server-side action template
- Add action-{id}-{index} IDs to action buttons
- Add action-status-{id}-{index} status divs for each action
- Match client-side template structure for consistency
- Fixes 'Action elements not found' error
* fix(api): Fix indentation error in execute_plugin_action function
- Fix incorrect else block indentation that caused 500 errors
- Correct indentation for OAuth flow and simple script execution paths
- Resolves syntax error preventing plugin actions from executing
* fix(api): Improve error handling for plugin actions and config saves
- Add better JSON parsing error handling with request details
- Add detailed permission error messages for secrets file saves
- Include file path and permission status in error responses
- Helps diagnose 400 errors on action execution and 500 errors on config saves
* fix(api): Add detailed permission error handling for secrets config saves
- Add PermissionError-specific handling with permission checks
- Include directory and file permission status in error logs
- Provide more helpful error messages with file paths
- Helps diagnose permission issues when saving config_secrets.json
* fix(config): Add permission check and actionable error message for config saves
- Check file writability before attempting write
- Show file owner and current permissions in error message
- Provide exact command to fix permissions (chown + chmod)
- Helps diagnose and resolve permission issues with config_secrets.json
* fix(config): Preserve detailed permission error messages
- Handle PermissionError separately to preserve detailed error messages
- Ensure actionable permission fix commands are included in error response
- Prevents detailed error messages from being lost in exception chain
* fix(config): Remove overly strict pre-write permission check
- Remove pre-write file existence/writability check that was blocking valid writes
- Let actual file write operation determine success/failure
- Provide detailed error messages only when write actually fails
- Fixes regression where config_secrets.json saves were blocked unnecessarily
* fix(config): Use atomic writes for config_secrets.json to handle permission issues
- Write to temp file first, then atomically move to final location
- Works even when existing file isn't writable (as long as directory is writable)
- Matches pattern used elsewhere in codebase (disk_cache, atomic_manager)
- Fixes permission errors when saving secrets configuration
* chore: Update music plugin submodule to include live_priority fix
* fix(plugins): Improve plugin ID determination in dynamic button generation
- Update generateFormFromSchema to pass currentPluginConfig?.pluginId and add data attributes
- Update generateSimpleConfigForm to pass currentPluginConfig?.pluginId and add data attributes
- Scope fallback 6 DOM lookup to button context instead of document-wide search
- Ensures correct plugin tab selection when multiple plugins are present
- Maintains existing try/catch error handling and logging
* chore: Update music plugin submodule to fix has_live_priority enabled attribute
* chore: Update music plugin submodule - remove redundant music_priority_mode
* fix(web-ui): Fix file upload widget detection for nested plugin properties
- Added helper function to get schema properties by full key path
- Enhanced x-widget detection to check both property object and schema directly
- Improved upload config retrieval with fallback to schema
- Added debug logging for file-upload widget detection
- Fixes issue where static-image plugin file upload widget was not rendering
The file upload widget was not being detected for nested properties like
image_config.images because the x-widget attribute wasn't being checked
in the schema directly. This fix ensures the widget is properly detected
and rendered even when nested deep in the configuration structure.
* fix(web-ui): Improve file upload widget detection with direct schema fallback
- Fixed getSchemaProperty helper function to correctly navigate nested paths
- Added direct schema lookup fallback for image_config.images path
- Enhanced debug logging to diagnose widget detection issues
- Simplified widget detection logic while maintaining robustness
* fix(web-ui): Add aggressive schema lookup for file-upload widget detection
- Always try direct schema navigation for image_config.images
- Added general direct lookup fallback if getSchemaProperty fails
- Enhanced debug logging with schema existence checks
- Prioritize schema lookup over prop object for x-widget detection
* fix(web-ui): Add direct check for top-level images field in file upload detection
- Added specific check for top-level 'images' field (flattened schema)
- Enhanced debug logging to show all x-widget detection attempts
- Improved widget detection to check prop object more thoroughly
* fix(web-ui): Prioritize prop object for x-widget detection
- Check prop object first (should have x-widget from schema)
- Then fall back to schema lookup
- Enhanced debug logging to show all detection attempts
* fix(web-ui): Add aggressive direct detection for images file upload widget
- Added direct check for 'images' field in schema.properties.images
- Multiple fallback detection methods (direct, prop object, schema lookup)
- Simplified logic to explicitly check for file-upload widget
- Enhanced debug logging to show detection path
* fix(web-ui): Add file upload widget support to server-side Jinja2 template
- Added check for x-widget: file-upload in array field rendering
- Renders file upload drop zone with drag-and-drop support
- Displays uploaded images list with delete and schedule buttons
- Falls back to comma-separated text input for regular arrays
- Fixes file upload widget not appearing in static-image plugin
* feat(web-ui): Add route to serve plugin asset files from assets directory
- Added /assets/plugins/<plugin_id>/uploads/<filename> route
- Serves uploaded images and other assets with proper content types
- Includes security checks to prevent directory traversal
- Fixes 404 errors when displaying uploaded plugin images
* fix(web-ui): Fix import for send_from_directory in plugin assets route
* feat(web-ui): Load uploaded images from metadata file when rendering config form
- Populates images field from .metadata.json if not in config
- Ensures uploaded images appear in form even before config is saved
- Merges metadata images with existing config images to avoid duplicates
* fix(web-ui): Fix PROJECT_ROOT reference in image metadata loading
* docs(web-ui): Add reminder to save configuration after file upload
- Added informational note below upload widget
- Reminds users to save config after uploading files
- Uses amber color and info icon for visibility
* fix(web-ui): Move plugin asset serving route to main app
- Moved /assets/plugins/... route from api_v3 blueprint to main app
- Blueprint has /api/v3 prefix, but route needs to be at /assets/...
- Fixes 404 errors when trying to display uploaded images
- Route must be on main app for correct URL path
* security(web-ui): Fix path containment check in plugin asset serving
- Replace string startswith() with proper path resolution using os.path.commonpath()
- Prevents prefix-based directory traversal bypasses
- Uses resolved absolute paths to ensure true path containment
- Handles ValueError for cross-drive paths (Windows compatibility)
* security(web-ui): Remove traceback exposure from plugin asset serving errors
- Return generic error message instead of full traceback in production
- Log exceptions server-side using app.logger.exception()
- Only include detailed error information when app.debug is True
- Prevents leaking internal implementation details to clients
* fix(web-ui): Assign currentPluginConfig to window for template access
- Assign currentPluginConfig to window.currentPluginConfig when building the object
- Fixes empty pluginId in template interpolation for plugin action buttons
- Ensures window.currentPluginConfig?.pluginId is available in onclick handlers
- Prevents executePluginAction from receiving empty pluginId parameter
* chore: Update music plugin submodule to include .gitignore
---------
Co-authored-by: Chuck <chuck@example.com>
This commit is contained in:
@@ -2509,17 +2509,75 @@
|
||||
// Only log once per plugin to avoid spam (Alpine.js may call this multiple times during rendering)
|
||||
if (!this._configFormLogged || this._configFormLogged !== pluginId) {
|
||||
console.log('[DEBUG] generateConfigForm called for', pluginId, 'with', webUiActions?.length || 0, 'actions');
|
||||
// Debug: Check if image_config.images has x-widget in schema
|
||||
if (schema && schema.properties && schema.properties.image_config) {
|
||||
const imgConfig = schema.properties.image_config;
|
||||
if (imgConfig.properties && imgConfig.properties.images) {
|
||||
const imagesProp = imgConfig.properties.images;
|
||||
console.log('[DEBUG] Schema check - image_config.images:', {
|
||||
type: imagesProp.type,
|
||||
'x-widget': imagesProp['x-widget'],
|
||||
'has x-widget': 'x-widget' in imagesProp,
|
||||
keys: Object.keys(imagesProp)
|
||||
});
|
||||
}
|
||||
}
|
||||
this._configFormLogged = pluginId;
|
||||
}
|
||||
if (!schema || !schema.properties) {
|
||||
return this.generateSimpleConfigForm(config, webUiActions, pluginId);
|
||||
}
|
||||
|
||||
// Helper function to get schema property by full key path
|
||||
const getSchemaProperty = (schemaObj, keyPath) => {
|
||||
if (!schemaObj || !schemaObj.properties) return null;
|
||||
const keys = keyPath.split('.');
|
||||
let current = schemaObj.properties;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i];
|
||||
if (!current || !current[k]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prop = current[k];
|
||||
// If this is the last key, return the property
|
||||
if (i === keys.length - 1) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
// If this property has nested properties, navigate deeper
|
||||
if (prop && typeof prop === 'object' && prop.properties) {
|
||||
current = prop.properties;
|
||||
} else {
|
||||
// Can't navigate deeper
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const generateFieldHtml = (key, prop, value, prefix = '') => {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
const label = prop.title || key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
const description = prop.description || '';
|
||||
let html = '';
|
||||
|
||||
// Debug: Log property structure for arrays to help diagnose file-upload widget issues
|
||||
if (prop.type === 'array') {
|
||||
// Also check schema directly as fallback
|
||||
const schemaProp = getSchemaProperty(schema, fullKey);
|
||||
const xWidgetFromSchema = schemaProp ? (schemaProp['x-widget'] || schemaProp['x_widget']) : null;
|
||||
|
||||
console.log('[DEBUG generateFieldHtml] Array property:', fullKey, {
|
||||
'prop.x-widget': prop['x-widget'],
|
||||
'prop.x_widget': prop['x_widget'],
|
||||
'schema.x-widget': xWidgetFromSchema,
|
||||
'hasOwnProperty(x-widget)': prop.hasOwnProperty('x-widget'),
|
||||
'x-widget in prop': 'x-widget' in prop,
|
||||
'all prop keys': Object.keys(prop),
|
||||
'schemaProp keys': schemaProp ? Object.keys(schemaProp) : 'null'
|
||||
});
|
||||
}
|
||||
|
||||
// Handle nested objects
|
||||
if (prop.type === 'object' && prop.properties) {
|
||||
@@ -2575,6 +2633,12 @@
|
||||
const nestedValue = hasValue ? nestedConfig[nestedKey] :
|
||||
(nestedProp.default !== undefined ? nestedProp.default :
|
||||
(isNestedObject ? {} : (nestedProp.type === 'array' ? [] : (nestedProp.type === 'boolean' ? false : ''))));
|
||||
|
||||
// Debug logging for file-upload widgets
|
||||
if (nestedProp.type === 'array' && (nestedProp['x-widget'] === 'file-upload' || nestedProp['x_widget'] === 'file-upload')) {
|
||||
console.log('[DEBUG] Found file-upload widget in nested property:', nestedKey, 'fullKey:', fullKey + '.' + nestedKey, 'prop:', nestedProp);
|
||||
}
|
||||
|
||||
html += generateFieldHtml(nestedKey, nestedProp, nestedValue, fullKey);
|
||||
});
|
||||
|
||||
@@ -2651,11 +2715,59 @@
|
||||
html += helpText;
|
||||
}
|
||||
} else if (prop.type === 'array') {
|
||||
// Check if this is a file upload widget
|
||||
if (prop['x-widget'] === 'file-upload') {
|
||||
// AGGRESSIVE file upload widget detection
|
||||
// For 'images' field in static-image plugin, always check schema directly
|
||||
let isFileUpload = false;
|
||||
let uploadConfig = {};
|
||||
|
||||
// Direct check: if this is the 'images' field and schema has it with x-widget
|
||||
if (fullKey === 'images' && schema && schema.properties && schema.properties.images) {
|
||||
const imagesSchema = schema.properties.images;
|
||||
if (imagesSchema['x-widget'] === 'file-upload' || imagesSchema['x_widget'] === 'file-upload') {
|
||||
isFileUpload = true;
|
||||
uploadConfig = imagesSchema['x-upload-config'] || imagesSchema['x_upload_config'] || {};
|
||||
console.log('[DEBUG] ✅ Direct detection: images field has file-upload widget', uploadConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: check prop object (should have x-widget if schema loaded correctly)
|
||||
if (!isFileUpload) {
|
||||
const xWidgetFromProp = prop['x-widget'] || prop['x_widget'] || prop.xWidget;
|
||||
if (xWidgetFromProp === 'file-upload') {
|
||||
isFileUpload = true;
|
||||
uploadConfig = prop['x-upload-config'] || prop['x_upload_config'] || {};
|
||||
console.log('[DEBUG] ✅ Detection via prop object');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: schema property lookup
|
||||
if (!isFileUpload) {
|
||||
let schemaProp = getSchemaProperty(schema, fullKey);
|
||||
if (!schemaProp && fullKey === 'images' && schema && schema.properties && schema.properties.images) {
|
||||
schemaProp = schema.properties.images;
|
||||
}
|
||||
const xWidgetFromSchema = schemaProp ? (schemaProp['x-widget'] || schemaProp['x_widget']) : null;
|
||||
if (xWidgetFromSchema === 'file-upload') {
|
||||
isFileUpload = true;
|
||||
uploadConfig = schemaProp['x-upload-config'] || schemaProp['x_upload_config'] || {};
|
||||
console.log('[DEBUG] ✅ Detection via schema lookup');
|
||||
}
|
||||
}
|
||||
|
||||
// Debug logging for ALL array fields to diagnose
|
||||
console.log('[DEBUG] Array field check:', fullKey, {
|
||||
'isFileUpload': isFileUpload,
|
||||
'prop keys': Object.keys(prop),
|
||||
'prop.x-widget': prop['x-widget'],
|
||||
'schema.properties.images exists': !!(schema && schema.properties && schema.properties.images),
|
||||
'schema.properties.images.x-widget': (schema && schema.properties && schema.properties.images) ? schema.properties.images['x-widget'] : null,
|
||||
'uploadConfig': uploadConfig
|
||||
});
|
||||
|
||||
if (isFileUpload) {
|
||||
console.log('[DEBUG] ✅ Rendering file-upload widget for', fullKey, 'with config:', uploadConfig);
|
||||
// Use the file upload widget from plugins.html
|
||||
// We'll need to call a function that exists in the global scope
|
||||
const uploadConfig = prop['x-upload-config'] || {};
|
||||
const maxFiles = uploadConfig.max_files || 10;
|
||||
const allowedTypes = uploadConfig.allowed_types || ['image/png', 'image/jpeg', 'image/bmp', 'image/gif'];
|
||||
const maxSizeMB = uploadConfig.max_size_mb || 5;
|
||||
|
||||
Reference in New Issue
Block a user