Keep backup browser visible

This commit is contained in:
Mikei386
2026-06-14 12:27:49 +02:00
parent a068bf5689
commit 737af8c0f0
8 changed files with 28 additions and 14 deletions
-1
View File
@@ -1 +0,0 @@
29655e71df818c71341058d031570f204d423eea63de4e199190c44b217343cc backupper-2026.06.14.1-x86_64-1.txz
+1
View File
@@ -0,0 +1 @@
5da36c3d720851d8ebd5e905399d1a9deca056128d2e01ee3f17a61f7145c807 backupper-2026.06.14.2-x86_64-1.txz
+5 -2
View File
@@ -2,13 +2,16 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "backupper">
<!ENTITY author "Backupper Project">
<!ENTITY version "2026.06.14.1">
<!ENTITY version "2026.06.14.2">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "29655e71df818c71341058d031570f204d423eea63de4e199190c44b217343cc">
<!ENTITY packageSHA256 "5da36c3d720851d8ebd5e905399d1a9deca056128d2e01ee3f17a61f7145c807">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues">
<CHANGES>
### 2026.06.14.2
- Always show backup browser roots, display folder loading errors inline, and expose the running daemon version.
### 2026.06.14.1
- Match the backup source browser layout and controls to the snapshot file browser.
+4 -1
View File
@@ -2,13 +2,16 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "backupper">
<!ENTITY author "Backupper Project">
<!ENTITY version "2026.06.14.1">
<!ENTITY version "2026.06.14.2">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues">
<CHANGES>
### 2026.06.14.2
- Always show backup browser roots, display folder loading errors inline, and expose the running daemon version.
### 2026.06.14.1
- Match the backup source browser layout and controls to the snapshot file browser.
+3 -3
View File
@@ -9,10 +9,10 @@ Tag="backup restic snapshots restore"
<?php
$pluginRoot = '/plugins/backupper';
?>
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/backupper.css?v=202606141">
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/backupper.css?v=202606142">
<div id="backupper-app" data-api="<?= $pluginRoot ?>/api.php">
<header class="bu-header">
<div><h1>Backupper <span class="bu-version">2026.06.14.1</span></h1><p>Encrypted Restic backups for Unraid</p></div>
<div><h1>Backupper <span class="bu-version">2026.06.14.2</span></h1><p>Encrypted Restic backups for Unraid</p></div>
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die Backupper-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Connecting...</div>
</header>
<nav class="bu-tabs" aria-label="Backupper sections">
@@ -29,4 +29,4 @@ $pluginRoot = '/plugins/backupper';
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
</div>
<script>window.BACKUPPER_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
<script src="<?= $pluginRoot ?>/assets/backupper-2026.06.14.1.js"></script>
<script src="<?= $pluginRoot ?>/assets/backupper-2026.06.14.2.js"></script>
+1
View File
@@ -69,6 +69,7 @@
.bu-selection { background: var(--bu-panel-alt); border: 1px solid var(--bu-border); border-radius: 999px; color: var(--bu-text); font-size: 12px; padding: 4px 8px; }
.bu-browser-card input[type="checkbox"], .bu-table input[type="checkbox"] { accent-color: var(--bu-accent); height: 20px; width: 20px; }
.bu-count { align-items: center; border: 1px solid var(--bu-border); border-radius: 999px; display: inline-flex; font-size: 13px; font-weight: 700; padding: 7px 11px; }
.bu-inline-error { background: rgba(180,35,47,.08); border: 1px solid var(--bu-bad); border-radius: 5px; color: var(--bu-bad); font-weight: 600; margin: 10px 0; padding: 10px 12px; }
#backupper-app .bu-button {
appearance: none;
+14 -7
View File
@@ -6,7 +6,8 @@
const health = document.getElementById('bu-health');
const toast = document.getElementById('bu-toast');
const tooltip = document.getElementById('bu-tooltip');
const state = { config: null, runs: [], view: 'dashboard', snapshots: [], selectedSnapshot: null, snapshotRepo: null, files: [], browserPath: '/', restoreIncludes: [], backupSelections: [], sourceBrowserPath: '/', sourceDirectories: [] };
const sourceRoots = ['/mnt/user','/mnt/disks','/mnt/remotes','/boot'].map(path=>({name:path,path}));
const state = { config: null, runs: [], view: 'dashboard', snapshots: [], selectedSnapshot: null, snapshotRepo: null, files: [], browserPath: '/', restoreIncludes: [], backupSelections: [], sourceBrowserPath: '/', sourceDirectories: sourceRoots, sourceBrowserError: '' };
const fieldHelp = {
name: 'Frei wählbarer Anzeigename. Beispiel: Appdata täglich oder VM Home Assistant.',
@@ -183,9 +184,9 @@
async function load() {
try {
const [config, runs] = await Promise.all([api('/v1/config'), api('/v1/runs')]);
const [config, runs, daemon] = await Promise.all([api('/v1/config'), api('/v1/runs'), api('/v1/health')]);
state.config = config; state.runs = runs;
health.textContent = 'Daemon online'; health.className = 'bu-health ok'; render();
health.textContent = `Daemon online (${daemon.version || 'unknown'})`; health.className = 'bu-health ok'; render();
} catch (error) {
health.textContent = 'Daemon unavailable'; health.className = 'bu-health bad';
content.innerHTML = `<div class="bu-card"><h2>Connection failed</h2><p>${esc(error.message)}</p></div>`;
@@ -219,7 +220,7 @@
function jobForm(job = {}) {
state.backupSelections = (job.sources || []).filter(s=>s.path).map(s=>s.path);
const source = (job.sources || []).filter(s=>s.workloadId).map(s=>`@${s.workloadId}`).join('\n');
state.sourceBrowserPath = '/'; state.sourceDirectories = [];
state.sourceBrowserPath = '/'; state.sourceDirectories = sourceRoots; state.sourceBrowserError = '';
const options = state.config.repositories.map(r => `<option value="${esc(r.id)}" ${r.id===job.repositoryId?'selected':''}>${esc(r.name)}</option>`).join('');
content.innerHTML = `<section class="bu-card"><h2>${job.id ? 'Edit' : 'New'} backup job</h2><form id="bu-job-form" class="bu-form">
<input type="hidden" name="id" value="${esc(job.id || '')}"><label>Name<input name="name" required value="${esc(job.name || '')}"></label>
@@ -234,7 +235,7 @@
<label><span>Enabled</span><input name="enabled" type="checkbox" ${job.enabled !== false?'checked':''}></label>
<div class="wide bu-actions">${button('Save','submit-job','primary')}${button('Cancel','cancel-form')}</div></form></section>`;
enhanceTooltips(content);
loadSourceDirectories('/');
renderSourceBrowser();
}
function renderSourceBrowser() {
@@ -243,17 +244,23 @@
const selected = new Set(state.backupSelections);
const rows = state.sourceDirectories.map(item => `<tr><td><input type="checkbox" class="bu-source-select" value="${esc(item.path)}" ${selected.has(item.path)?'checked':''}></td><td>dir</td><td>${esc(item.name)}</td><td>${esc(item.path)}</td><td>${button('Open',`open-source-dir:${encodeURIComponent(item.path)}`)}</td></tr>`).join('');
const chosen = state.backupSelections.length ? state.backupSelections.map(path=>`<span class="bu-selection">${esc(path)}</span>`).join('') : '<span class="bu-muted">No folders selected.</span>';
host.innerHTML = `<section class="bu-browser-card"><div class="bu-toolbar"><div><h3>Backup source browser</h3><span class="bu-muted">${esc(state.sourceBrowserPath)}</span></div><div class="bu-actions">${state.sourceBrowserPath!=='/'?button('Select current folder','select-current-source'):''}${state.sourceBrowserPath!=='/'?button('Up','source-up'):''}<span class="bu-count">Selected: ${state.backupSelections.length}</span></div></div><div class="bu-selection-list">${chosen}</div><div class="bu-browser-scroll"><table class="bu-table"><thead><tr><th>Select</th><th>Type</th><th>Name</th><th>Path</th><th></th></tr></thead><tbody>${rows||'<tr><td colspan="5" class="bu-empty">No subfolders found.</td></tr>'}</tbody></table></div></section>`;
const error = state.sourceBrowserError ? `<div class="bu-inline-error">${esc(state.sourceBrowserError)}</div>` : '';
host.innerHTML = `<section class="bu-browser-card"><div class="bu-toolbar"><div><h3>Backup source browser</h3><span class="bu-muted">${esc(state.sourceBrowserPath)}</span></div><div class="bu-actions">${state.sourceBrowserPath!=='/'?button('Select current folder','select-current-source'):''}${state.sourceBrowserPath!=='/'?button('Up','source-up'):''}<span class="bu-count">Selected: ${state.backupSelections.length}</span></div></div>${error}<div class="bu-selection-list">${chosen}</div><div class="bu-browser-scroll"><table class="bu-table"><thead><tr><th>Select</th><th>Type</th><th>Name</th><th>Path</th><th></th></tr></thead><tbody>${rows||'<tr><td colspan="5" class="bu-empty">No subfolders found.</td></tr>'}</tbody></table></div></section>`;
enhanceTooltips(host);
}
async function loadSourceDirectories(path) {
if (path === '/') {
state.sourceDirectories = sourceRoots; state.sourceBrowserPath = '/'; state.sourceBrowserError = ''; renderSourceBrowser(); return;
}
try {
state.sourceDirectories = await api(`/v1/filesystem/directories?path=${encodeURIComponent(path)}`);
state.sourceBrowserPath = path;
state.sourceBrowserError = '';
renderSourceBrowser();
} catch (error) {
notify(error.message, true);
state.sourceBrowserError = `Folder could not be loaded: ${error.message}`;
renderSourceBrowser();
}
}