From 6da8f80477199c2f8af0a6143024e8e8de7ddced Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 23 May 2026 14:31:36 +0100 Subject: [PATCH] feat(backup): tabbed location editor (Connection / Retention / Advanced) The expanded location row was one long form. Split it into tabs so it opens showing only the Connection fields. Retention moves from a stacked section into its own tab, and the advanced overrides (URI/SSH port/append-only) get their own tab instead of the inline disclosure from the previous pass. Field grouping is metadata-driven: locFieldGroups partitions a type's fields into Connection vs Advanced via the configs.json "advanced" flag (with LOC_ADVANCED_SUFFIXES as the legacy fallback). Type changes rebuild both the Connection and Advanced panels since advanced fields are type-dependent too. Save still reads every field across all panels (hidden tabs stay in the DOM). Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- .../libreportal/frontend/css/backup.css | 55 +++--- .../js/components/backup/backup-page.js | 185 ++++++++++-------- 2 files changed, 126 insertions(+), 114 deletions(-) diff --git a/containers/libreportal/frontend/css/backup.css b/containers/libreportal/frontend/css/backup.css index ede4a82..047bed5 100755 --- a/containers/libreportal/frontend/css/backup.css +++ b/containers/libreportal/frontend/css/backup.css @@ -723,47 +723,48 @@ border-top: 1px dashed rgba(var(--text-rgb), 0.08); } -/* Full-width "Advanced" disclosure in the location editor's Connection - section. Reveals the advanced fields (URI override, SSH port, append-only). */ -.backup-loc-advanced-toggle { +/* Tabbed location editor (Connection | Retention | Advanced). Splits the + formerly long single form into one panel per concern. */ +.backup-loc-tabs { display: flex; - align-items: center; - gap: 8px; - width: 100%; - margin-top: 14px; - padding: 10px 12px; - background: rgba(var(--text-rgb), 0.03); - border: 1px solid rgba(var(--text-rgb), 0.08); - border-radius: 8px; - color: var(--text-primary); + gap: 4px; + border-bottom: 1px solid rgba(var(--text-rgb), 0.10); + margin-bottom: 16px; +} + +.backup-loc-tab { + appearance: none; + background: transparent; + border: none; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + padding: 8px 14px; + color: rgba(var(--text-rgb), 0.6); font: inherit; font-size: 0.9rem; font-weight: 600; cursor: pointer; - transition: background 0.15s ease; + transition: color 0.15s ease, border-color 0.15s ease; } -.backup-loc-advanced-toggle:hover { - background: rgba(var(--text-rgb), 0.06); +.backup-loc-tab:hover { + color: var(--text-primary); } -.backup-loc-advanced-chevron { - transition: transform 0.15s ease; - flex-shrink: 0; +.backup-loc-tab.active { + color: var(--accent); + border-bottom-color: var(--accent); } -.backup-loc-advanced-toggle.open .backup-loc-advanced-chevron { - transform: rotate(90deg); -} - -.backup-loc-advanced { - margin-top: 12px; -} - -.backup-loc-advanced[hidden] { +.backup-loc-tab-panel[hidden] { display: none; } +.backup-loc-tab-panel > .category-description { + margin-top: 0; + margin-bottom: 12px; +} + .backup-form-footer { display: flex; justify-content: flex-end; diff --git a/containers/libreportal/frontend/js/components/backup/backup-page.js b/containers/libreportal/frontend/js/components/backup/backup-page.js index 816f003..4b66dee 100644 --- a/containers/libreportal/frontend/js/components/backup/backup-page.js +++ b/containers/libreportal/frontend/js/components/backup/backup-page.js @@ -62,6 +62,11 @@ const BACKUP_LOC_FIELDS_BY_TYPE = { rclone: ['NAME', 'ENGINE', 'TYPE', 'URI', 'APPEND_ONLY'] }; +// Suffixes that live in the editor's "Advanced" tab. configs.json can flag +// more via a **ADVANCED** comment marker; this set keeps the known overrides +// advanced even on legacy location.configs that predate the marker. +const LOC_ADVANCED_SUFFIXES = new Set(['URI', 'SSH_PORT', 'APPEND_ONLY']); + function backupRetentionDetectPreset(values, includeInherit = false) { const norm = (v) => (v == null ? '' : String(v).trim()); for (const [key, p] of Object.entries(BACKUP_RETENTION_PRESETS)) { @@ -198,15 +203,19 @@ class BackupPage { return; } - const advToggle = e.target.closest('[data-action="toggle-loc-advanced"]'); - if (advToggle) { - const sec = document.getElementById(advToggle.dataset.target); - if (sec) { - const show = sec.hasAttribute('hidden'); - sec.toggleAttribute('hidden', !show); - advToggle.setAttribute('aria-expanded', show ? 'true' : 'false'); - advToggle.classList.toggle('open', show); - } + const locTab = e.target.closest('[data-action="loc-tab"]'); + if (locTab) { + const tabIdx = locTab.dataset.loc; + const tabName = locTab.dataset.tab; + const root = locTab.closest('.backup-location-config') || document; + root.querySelectorAll(`[data-action="loc-tab"][data-loc="${tabIdx}"]`).forEach(b => { + const on = b === locTab; + b.classList.toggle('active', on); + b.setAttribute('aria-selected', on ? 'true' : 'false'); + }); + root.querySelectorAll(`[data-tab-panel][data-loc="${tabIdx}"]`).forEach(p => { + p.toggleAttribute('hidden', p.dataset.tabPanel !== tabName); + }); return; } @@ -659,7 +668,7 @@ class BackupPage { renderLocationDetailsBody(l) { const idx = l.idx; - const connectionFields = BACKUP_LOC_FIELDS_BY_TYPE[l.type] || BACKUP_LOC_FIELDS_BY_TYPE.local; + const groups = this.locFieldGroups(idx, l.type); const retentionValues = { last: l.custom_retention ? (l.keep_last || '') : '', daily: l.custom_retention ? (l.keep_daily || '') : '', @@ -668,34 +677,37 @@ class BackupPage { yearly: l.custom_retention ? (l.keep_yearly || '') : '' }; + const tab = (id, label) => ` + `; + const advancedBody = groups.advanced.length + ? this.renderLocFields(idx, groups.advanced, l) + : `
No advanced options for this location type.
`; + return `
-
-
-
-

Connection

-

How LibrePortal connects to this storage location.

-
-
-
+
+ ${tab('connection', 'Connection')} + ${tab('retention', 'Retention')} + ${tab('advanced', 'Advanced')} +
+
+

How LibrePortal connects to this storage location.

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

Retention

-

When to delete old backups from this location.

-
-
-
+ +
- - `; - } - return html; + return { connection, advanced }; } tagFieldsForSave(container) {