Keep backup browser visible
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user