Compare commits
2 Commits
5c6f4f4a2c
...
501edda217
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
501edda217 | ||
|
|
4568ec51ef |
@ -865,6 +865,50 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Export dropdown in the backup page header (Configuration tab). */
|
||||
#backup-page-header .page-header-actions {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.backup-export-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
z-index: 50;
|
||||
min-width: 210px;
|
||||
padding: 6px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border, var(--border-color));
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--card-shadow, 0 8px 24px rgba(0, 0, 0, 0.25));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.backup-export-menu[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.backup-export-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 8px 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.backup-export-menu-item:hover {
|
||||
background: rgba(var(--text-rgb), 0.08);
|
||||
}
|
||||
|
||||
.backup-warning-banner [data-action="export-passwords"] {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
@ -55,6 +55,16 @@
|
||||
Refresh
|
||||
</button>
|
||||
<button class="backup-primary-btn" id="backup-primary-action"></button>
|
||||
<div class="backup-export-menu" id="backup-export-menu" role="menu" hidden>
|
||||
<button type="button" class="backup-export-menu-item" data-action="export-passwords" role="menuitem">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Repository Passwords
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="backup-page-body">
|
||||
|
||||
@ -136,6 +136,14 @@ class BackupPage {
|
||||
// the URL via parseTabFromUrl() at init time.
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
// Clicking outside the export dropdown (and not on its trigger) closes it.
|
||||
const exportMenu = document.getElementById('backup-export-menu');
|
||||
if (exportMenu && !exportMenu.hidden
|
||||
&& !e.target.closest('#backup-export-menu')
|
||||
&& !e.target.closest('#backup-primary-action')) {
|
||||
this.toggleExportMenu(false);
|
||||
}
|
||||
|
||||
const tabBtn = e.target.closest('.backup-layout .sidebar .category[data-backup-tab]');
|
||||
if (tabBtn) {
|
||||
this.switchTab(tabBtn.dataset.backupTab);
|
||||
@ -201,7 +209,7 @@ class BackupPage {
|
||||
if (engineBtn) { this.openEngineDetailsModal(engineBtn); return; }
|
||||
|
||||
const exportBtn = e.target.closest('[data-action="export-passwords"]');
|
||||
if (exportBtn) { this.exportRepositoryPasswords(exportBtn); return; }
|
||||
if (exportBtn) { this.toggleExportMenu(false); this.exportRepositoryPasswords(exportBtn); return; }
|
||||
|
||||
const dismissWarn = e.target.closest('[data-action="dismiss-config-warning"]');
|
||||
if (dismissWarn) {
|
||||
@ -381,6 +389,8 @@ class BackupPage {
|
||||
updatePrimaryAction() {
|
||||
const btn = document.getElementById('backup-primary-action');
|
||||
if (!btn) return;
|
||||
// Switching tabs always closes the export dropdown.
|
||||
this.toggleExportMenu(false);
|
||||
if (this.currentTab === 'locations') {
|
||||
btn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@ -390,6 +400,21 @@ class BackupPage {
|
||||
Add location
|
||||
`;
|
||||
btn.dataset.intent = 'add-location';
|
||||
btn.removeAttribute('aria-haspopup');
|
||||
} else if (this.currentTab === 'configuration') {
|
||||
btn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Export
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
`;
|
||||
btn.dataset.intent = 'export-menu';
|
||||
btn.setAttribute('aria-haspopup', 'menu');
|
||||
} else {
|
||||
btn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@ -400,6 +425,7 @@ class BackupPage {
|
||||
Backup all apps
|
||||
`;
|
||||
btn.dataset.intent = 'backup-all';
|
||||
btn.removeAttribute('aria-haspopup');
|
||||
}
|
||||
}
|
||||
|
||||
@ -407,11 +433,22 @@ class BackupPage {
|
||||
const intent = document.getElementById('backup-primary-action')?.dataset.intent;
|
||||
if (intent === 'add-location') {
|
||||
this.openAddLocationModal();
|
||||
} else if (intent === 'export-menu') {
|
||||
this.toggleExportMenu();
|
||||
} else {
|
||||
this.runBackupAllApps();
|
||||
}
|
||||
}
|
||||
|
||||
toggleExportMenu(force) {
|
||||
const menu = document.getElementById('backup-export-menu');
|
||||
const btn = document.getElementById('backup-primary-action');
|
||||
if (!menu) return;
|
||||
const show = typeof force === 'boolean' ? force : menu.hidden;
|
||||
menu.hidden = !show;
|
||||
if (btn) btn.setAttribute('aria-expanded', show ? 'true' : 'false');
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderDashboard();
|
||||
this.renderLocations();
|
||||
@ -808,14 +845,6 @@ class BackupPage {
|
||||
<strong>Keep your LibrePortal config backed up offline.</strong>
|
||||
<span>Repository passwords live inside the config directory. Without that backup, snapshots cannot be decrypted by anyone — including you.</span>
|
||||
</div>
|
||||
<button type="button" class="backup-secondary-btn" data-action="export-passwords">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Export Repository Passwords
|
||||
</button>
|
||||
<button type="button" class="backup-warning-banner-close" data-action="dismiss-config-warning" title="Dismiss this warning" aria-label="Dismiss this warning">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user