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.
-
-
-
+
+
When to delete old backups from this location.
${this.formRetention(`CFG_BACKUP_LOC_${idx}_`, retentionValues, true)}
+
+
Overrides most locations don't need.
+
+ ${advancedBody}
+
+