mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-11 05:13:01 +00:00
fix(plugins): Resolve plugin action button errors and config save permission issues (#162)
* 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
---------
Co-authored-by: Chuck <chuck@example.com>
This commit is contained in:
@@ -438,11 +438,52 @@ class ConfigManager:
|
||||
path_obj = Path(path_to_save)
|
||||
ensure_directory_permissions(path_obj.parent, get_config_dir_mode())
|
||||
|
||||
with open(path_to_save, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
# Use atomic write: write to temp file first, then move atomically
|
||||
# This works even if the existing file isn't writable (as long as directory is writable)
|
||||
import tempfile
|
||||
file_mode = get_config_file_mode(path_obj)
|
||||
|
||||
# Set proper file permissions after writing
|
||||
ensure_file_permissions(path_obj, get_config_file_mode(path_obj))
|
||||
# Create temp file in same directory to ensure atomic move works
|
||||
temp_fd, temp_path = tempfile.mkstemp(
|
||||
suffix='.json',
|
||||
dir=str(path_obj.parent),
|
||||
text=True
|
||||
)
|
||||
|
||||
try:
|
||||
# Write to temp file
|
||||
with os.fdopen(temp_fd, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
|
||||
# Set permissions on temp file before moving
|
||||
try:
|
||||
os.chmod(temp_path, file_mode)
|
||||
except OSError:
|
||||
pass # Non-critical if chmod fails
|
||||
|
||||
# Atomically move temp file to final location
|
||||
# This works even if target file exists and isn't writable
|
||||
os.replace(temp_path, str(path_obj))
|
||||
temp_path = None # Mark as moved so we don't try to clean it up
|
||||
|
||||
# Ensure final file has correct permissions
|
||||
try:
|
||||
ensure_file_permissions(path_obj, file_mode)
|
||||
except OSError as perm_error:
|
||||
# If we can't set permissions but file was written, log warning but don't fail
|
||||
self.logger.warning(
|
||||
f"File {path_to_save} was written successfully but could not set permissions: {perm_error}. "
|
||||
f"This may cause issues if the file needs to be accessible by other users."
|
||||
)
|
||||
finally:
|
||||
# Clean up temp file if it still exists (move failed)
|
||||
if temp_path and os.path.exists(temp_path):
|
||||
try:
|
||||
os.remove(temp_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.logger.info(f"{file_type.capitalize()} configuration successfully saved to {os.path.abspath(path_to_save)}")
|
||||
|
||||
@@ -461,8 +502,43 @@ class ConfigManager:
|
||||
f"The file on disk is valid, but in-memory config may be stale."
|
||||
)
|
||||
|
||||
except (IOError, OSError, PermissionError) as e:
|
||||
error_msg = f"Error writing {file_type} configuration to file {os.path.abspath(path_to_save)}"
|
||||
except PermissionError as e:
|
||||
# Provide helpful error message with fix instructions
|
||||
import stat
|
||||
try:
|
||||
import pwd
|
||||
if path_obj.exists():
|
||||
file_stat = path_obj.stat()
|
||||
current_mode = stat.filemode(file_stat.st_mode)
|
||||
try:
|
||||
file_owner = pwd.getpwuid(file_stat.st_uid).pw_name
|
||||
except (ImportError, KeyError):
|
||||
file_owner = f"UID {file_stat.st_uid}"
|
||||
error_msg = (
|
||||
f"Cannot write to {file_type} configuration file {os.path.abspath(path_to_save)}. "
|
||||
f"File is owned by {file_owner} with permissions {current_mode}. "
|
||||
f"To fix, run: sudo chown $USER:$(id -gn) {path_to_save} && sudo chmod 664 {path_to_save}"
|
||||
)
|
||||
else:
|
||||
# File doesn't exist - check directory permissions
|
||||
dir_stat = path_obj.parent.stat()
|
||||
dir_mode = stat.filemode(dir_stat.st_mode)
|
||||
try:
|
||||
dir_owner = pwd.getpwuid(dir_stat.st_uid).pw_name
|
||||
except (ImportError, KeyError):
|
||||
dir_owner = f"UID {dir_stat.st_uid}"
|
||||
error_msg = (
|
||||
f"Cannot create {file_type} configuration file {os.path.abspath(path_to_save)}. "
|
||||
f"Directory is owned by {dir_owner} with permissions {dir_mode}. "
|
||||
f"To fix, run: sudo chown $USER:$(id -gn) {path_obj.parent} && sudo chmod 775 {path_obj.parent}"
|
||||
)
|
||||
except Exception:
|
||||
# Fallback to generic message if we can't get file info
|
||||
error_msg = f"Error writing {file_type} configuration to file {os.path.abspath(path_to_save)}: {str(e)}"
|
||||
self.logger.error(error_msg, exc_info=True)
|
||||
raise ConfigError(error_msg, config_path=path_to_save) from e
|
||||
except (IOError, OSError) as e:
|
||||
error_msg = f"Error writing {file_type} configuration to file {os.path.abspath(path_to_save)}: {str(e)}"
|
||||
self.logger.error(error_msg, exc_info=True)
|
||||
raise ConfigError(error_msg, config_path=path_to_save) from e
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user