From 0b540b8d8ac1fc535d02abdbb34f78ee66fb2994 Mon Sep 17 00:00:00 2001 From: bobokun Date: Thu, 28 Aug 2025 12:15:53 -0400 Subject: [PATCH] feat(ui): add quick action settings persistence and improve command form handling - Add localStorage persistence for quick action checkboxes and log level - Implement collectAllFormValues method for comprehensive form data collection - Simplify config initialization to create empty config.yml instead of copying sample - Update command section handling to use all form values for proper saving --- VERSION | 2 +- modules/util.py | 16 +++--- web-ui/js/app.js | 12 +++-- web-ui/js/components/command-panel.js | 74 +++++++++++++++++++++++++++ web-ui/js/components/config-form.js | 45 ++++++++++++++++ 5 files changed, 136 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index bba594f..da153cc 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.5.6-develop5 +4.5.6-develop6 diff --git a/modules/util.py b/modules/util.py index 98adbf2..840d9a0 100755 --- a/modules/util.py +++ b/modules/util.py @@ -300,7 +300,7 @@ def ensure_config_dir_initialized(config_dir) -> str: Ensure the config directory exists and is initialized: - Creates the config directory - Creates logs/ and .backups/ subdirectories - - Seeds a default config.yml from bundled config/config.yml.sample if no *.yml/*.yaml present + - Creates an empty config.yml if no *.yml/*.yaml present Returns the absolute config directory as a string. """ p = Path(config_dir).expanduser().resolve() @@ -310,14 +310,12 @@ def ensure_config_dir_initialized(config_dir) -> str: has_yaml = any(p.glob("*.yml")) or any(p.glob("*.yaml")) if not has_yaml: - sample = runtime_path("config", "config.yml.sample") - if sample.exists(): - dest = p / "config.yml" - try: - shutil.copyfile(sample, dest) - except Exception: - # Non-fatal; if copy fails, user can create a config manually - pass + dest = p / "config.yml" + try: + dest.touch() # Create empty file + except Exception: + # Non-fatal; if creation fails, user can create a config manually + pass return str(p) diff --git a/web-ui/js/app.js b/web-ui/js/app.js index ef663c5..5cd52d6 100755 --- a/web-ui/js/app.js +++ b/web-ui/js/app.js @@ -225,8 +225,6 @@ class QbitManageApp { this.backupConfig(); }); - // Theme toggle is handled by ThemeManager - // Undo button const undoBtn = get('undo-btn'); if (undoBtn) { @@ -442,7 +440,15 @@ class QbitManageApp { } try { - const processedData = this.configForm._postprocessDataForSave(this.currentSection, this.configForm.currentData); + // For commands section, collect all form values since commands should override env vars + let dataToProcess; + if (this.currentSection === 'commands') { + dataToProcess = this.configForm.collectAllFormValues(this.currentSection); + } else { + dataToProcess = this.configForm.currentData; + } + + const processedData = this.configForm._postprocessDataForSave(this.currentSection, dataToProcess); const isMultiRoot = this.configForm.schemas[this.currentSection]?.type === 'multi-root-object'; const dataToSave = isMultiRoot ? processedData : { [this.currentSection]: processedData }; diff --git a/web-ui/js/components/command-panel.js b/web-ui/js/components/command-panel.js index c5b9acf..6bb0b01 100755 --- a/web-ui/js/components/command-panel.js +++ b/web-ui/js/components/command-panel.js @@ -141,6 +141,9 @@ class CommandPanel { `; + + // Load saved quick action values after rendering + this.loadQuickActionValues(); } bindEvents() { @@ -175,6 +178,9 @@ class CommandPanel { this.showRunCommandsModal(); } }); + + // Bind quick action input change events for persistence + this.bindQuickActionPersistence(); } async executeQuickCommand(command) { @@ -475,6 +481,74 @@ class CommandPanel { this.show(); } } + + // Load saved quick action values from localStorage + loadQuickActionValues() { + if (!this.drawerContainer) return; + + // Get saved values, defaulting dry run to true if not previously saved + const savedDryRunValue = localStorage.getItem('qbm-quick-dry-run'); + const savedDryRun = savedDryRunValue !== null ? savedDryRunValue === 'true' : true; // Default to true + const savedSkipCleanup = localStorage.getItem('qbm-quick-skip-cleanup') === 'true'; + const savedSkipQbVersionCheck = localStorage.getItem('qbm-quick-skip-qb-version-check') === 'true'; + const savedLogLevel = localStorage.getItem('qbm-quick-log-level') || ''; + + const dryRunCheckbox = this.drawerContainer.querySelector('#dry-run-checkbox'); + const skipCleanupCheckbox = this.drawerContainer.querySelector('#quick-skip-cleanup-checkbox'); + const skipQbVersionCheckCheckbox = this.drawerContainer.querySelector('#quick-skip-qb-version-check-checkbox'); + const logLevelSelect = this.drawerContainer.querySelector('#quick-log-level-select'); + + if (dryRunCheckbox) dryRunCheckbox.checked = savedDryRun; + if (skipCleanupCheckbox) skipCleanupCheckbox.checked = savedSkipCleanup; + if (skipQbVersionCheckCheckbox) skipQbVersionCheckCheckbox.checked = savedSkipQbVersionCheck; + if (logLevelSelect) logLevelSelect.value = savedLogLevel; + + // Save the default value if it was set + if (savedDryRunValue === null) { + localStorage.setItem('qbm-quick-dry-run', 'true'); + } + } + + // Save quick action values to localStorage + saveQuickActionValues() { + if (!this.drawerContainer) return; + + const dryRunCheckbox = this.drawerContainer.querySelector('#dry-run-checkbox'); + const skipCleanupCheckbox = this.drawerContainer.querySelector('#quick-skip-cleanup-checkbox'); + const skipQbVersionCheckCheckbox = this.drawerContainer.querySelector('#quick-skip-qb-version-check-checkbox'); + const logLevelSelect = this.drawerContainer.querySelector('#quick-log-level-select'); + + const dryRun = dryRunCheckbox ? dryRunCheckbox.checked : false; + const skipCleanup = skipCleanupCheckbox ? skipCleanupCheckbox.checked : false; + const skipQbVersionCheck = skipQbVersionCheckCheckbox ? skipQbVersionCheckCheckbox.checked : false; + const logLevel = logLevelSelect ? logLevelSelect.value : ''; + + localStorage.setItem('qbm-quick-dry-run', dryRun); + localStorage.setItem('qbm-quick-skip-cleanup', skipCleanup); + localStorage.setItem('qbm-quick-skip-qb-version-check', skipQbVersionCheck); + localStorage.setItem('qbm-quick-log-level', logLevel); + } + + // Bind event listeners for quick action persistence + bindQuickActionPersistence() { + if (!this.drawerContainer) return; + + // Bind checkbox change events + const checkboxes = this.drawerContainer.querySelectorAll('#dry-run-checkbox, #quick-skip-cleanup-checkbox, #quick-skip-qb-version-check-checkbox'); + checkboxes.forEach(checkbox => { + checkbox.addEventListener('change', () => { + this.saveQuickActionValues(); + }); + }); + + // Bind select change event + const logLevelSelect = this.drawerContainer.querySelector('#quick-log-level-select'); + if (logLevelSelect) { + logLevelSelect.addEventListener('change', () => { + this.saveQuickActionValues(); + }); + } + } } export { CommandPanel }; diff --git a/web-ui/js/components/config-form.js b/web-ui/js/components/config-form.js index 0090dd4..ac4401a 100755 --- a/web-ui/js/components/config-form.js +++ b/web-ui/js/components/config-form.js @@ -1302,6 +1302,51 @@ class ConfigForm { * @param {object} obj - The object to clean up. * @returns {object} The cleaned up object. */ + /** + * Collects all current form values for a section, not just dirty ones + * This is used for sections like commands where we want to save all values + */ + collectAllFormValues(sectionName) { + const sectionConfig = this.schemas[sectionName]; + if (!sectionConfig || !sectionConfig.fields) { + return {}; + } + + const allValues = {}; + + // Iterate through all fields in the schema + sectionConfig.fields.forEach(field => { + if (field.name && field.type !== 'documentation' && field.type !== 'section_header') { + // Find the corresponding form input + const input = this.container.querySelector(`[name="${field.name}"]`); + + if (input) { + let value; + + if (input.type === 'checkbox') { + value = input.checked; + } else if (input.type === 'number') { + value = input.value ? parseFloat(input.value) : null; + } else { + value = input.value; + } + + // Handle default values for boolean fields + if (field.type === 'boolean' && value === null) { + value = field.default || false; + } + + allValues[field.name] = value; + } else if (field.default !== undefined) { + // Use default value if input not found + allValues[field.name] = field.default; + } + } + }); + + return allValues; + } + cleanupEmptyValues(obj) { if (obj === null || obj === undefined) { return null;