diff --git a/containers/libreportal/frontend/css/backup.css b/containers/libreportal/frontend/css/backup.css index bcb5e05..fe042c8 100755 --- a/containers/libreportal/frontend/css/backup.css +++ b/containers/libreportal/frontend/css/backup.css @@ -794,6 +794,12 @@ transform: translateX(16px); } +/* Enable/disable toggle in a location row header — sits between the row + info and the expand chevron, controlling the location without expanding it. */ +.backup-loc-enable-toggle { + margin-right: 6px; +} + /* Repo card extras */ .backup-repo-stats { display: flex; diff --git a/containers/libreportal/frontend/js/components/backup/backup-page.js b/containers/libreportal/frontend/js/components/backup/backup-page.js index c4cfdb2..6a0a489 100644 --- a/containers/libreportal/frontend/js/components/backup/backup-page.js +++ b/containers/libreportal/frontend/js/components/backup/backup-page.js @@ -52,14 +52,14 @@ const BACKUP_LOC_FIELD_DEFS = { }; const BACKUP_LOC_FIELDS_BY_TYPE = { - local: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'PATH_MODE', 'PATH', 'APPEND_ONLY'], - sftp: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'SSH_USER', 'SSH_HOST', 'SSH_PORT', 'SSH_PATH', 'SSH_AUTH', 'SSH_PASS', 'URI', 'APPEND_ONLY'], - rest: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'], - s3: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'S3_ACCESS_KEY', 'S3_SECRET_KEY', 'APPEND_ONLY'], - b2: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'B2_ACCOUNT_ID', 'B2_ACCOUNT_KEY', 'APPEND_ONLY'], - gs: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'], - azure: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'], - rclone: ['NAME', 'ENABLED', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'] + local: ['NAME', 'ENGINE', 'TYPE', 'PATH_MODE', 'PATH', 'APPEND_ONLY'], + sftp: ['NAME', 'ENGINE', 'TYPE', 'SSH_USER', 'SSH_HOST', 'SSH_PORT', 'SSH_PATH', 'SSH_AUTH', 'SSH_PASS', 'URI', 'APPEND_ONLY'], + rest: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'], + s3: ['NAME', 'ENGINE', 'TYPE', 'URI', 'S3_ACCESS_KEY', 'S3_SECRET_KEY', 'APPEND_ONLY'], + b2: ['NAME', 'ENGINE', 'TYPE', 'URI', 'B2_ACCOUNT_ID', 'B2_ACCOUNT_KEY', 'APPEND_ONLY'], + gs: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'], + azure: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'], + rclone: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'] }; function backupRetentionDetectPreset(values, includeInherit = false) { @@ -164,6 +164,13 @@ class BackupPage { return; } + const locEnable = e.target.closest('[data-action="toggle-location-enabled"]'); + if (locEnable) { + const cb = locEnable.querySelector('input[type="checkbox"]'); + this.setLocationEnabled(parseInt(locEnable.dataset.loc, 10), cb ? cb.checked : true); + return; + } + const locHeader = e.target.closest('[data-action="toggle-location"]'); if (locHeader) { this.toggleLocationExpand(parseInt(locHeader.dataset.loc, 10)); @@ -546,6 +553,10 @@ class BackupPage { · ${size} + + + + @@ -585,16 +596,31 @@ class BackupPage { return `
-

Connection

-
- ${this.renderLocFields(idx, connectionFields, l)} +
+
+
+

Connection

+

How LibrePortal connects to this storage location.

+
+
+
+
+ ${this.renderLocFields(idx, connectionFields, l)} +
-

Retention

-

When to delete old backups from this location.

-
- ${this.formRetention(`CFG_BACKUP_LOC_${idx}_`, retentionValues, true)} +
+
+
+

Retention

+

When to delete old backups from this location.

+
+
+
+
+ ${this.formRetention(`CFG_BACKUP_LOC_${idx}_`, retentionValues, true)} +
@@ -964,7 +990,6 @@ class BackupPage { "Custom…" is selected. */ formRetention(prefix, values, includeInherit = false) { const preset = backupRetentionDetectPreset(values, includeInherit); - const meta = BACKUP_RETENTION_PRESET_META[preset]; const presetOptions = Object.entries(BACKUP_RETENTION_PRESET_META) .filter(([k]) => k !== 'inherit-global' || includeInherit) .map(([k, v]) => ``) @@ -977,10 +1002,9 @@ class BackupPage { return `
-
${this.escape(meta?.hint || '')}
${customRetentionHidden}
@@ -1002,8 +1026,6 @@ class BackupPage { const prefix = block.dataset.retentionPrefix; const allowInherit = block.dataset.retentionAllowInherit === '1'; const preset = selectEl.value; - const hintEl = block.querySelector('[data-retention-hint]'); - if (hintEl) hintEl.textContent = BACKUP_RETENTION_PRESET_META[preset]?.hint || ''; if (preset === 'custom') { if (advanced) advanced.hidden = false; @@ -1437,6 +1459,20 @@ class BackupPage { /* ----- Generic save handler ----- */ + async setLocationEnabled(idx, enabled) { + const encoded = `CFG_BACKUP_LOC_${idx}_ENABLED=${enabled ? 'true' : 'false'}`; + try { + if (!window.tasksManager?.router) throw new Error('Task system not available'); + await window.tasksManager.router.routeAction('config_update', { + changes: `'${encoded.replace(/'/g, "'\\''")}'` + }); + this.notify(`${enabled ? 'Enabling' : 'Disabling'} this location…`, 'success'); + setTimeout(() => this.reloadAfterSave(), 2500); + } catch (err) { + this.notify(`Save failed: ${err.message || err}`, 'error'); + } + } + async saveSection(sectionId) { let scope; if (sectionId.startsWith('location-')) {