diff --git a/dist/urbm-2026.06.21.r007-x86_64-1.txz.sha256 b/dist/urbm-2026.06.21.r007-x86_64-1.txz.sha256 deleted file mode 100644 index 1066ab9..0000000 --- a/dist/urbm-2026.06.21.r007-x86_64-1.txz.sha256 +++ /dev/null @@ -1 +0,0 @@ -7b3b3c9fd00ce2aae592ea4ec84d2875afce72ca2cad2f24a7e075312a6c528f urbm-2026.06.21.r007-x86_64-1.txz diff --git a/dist/urbm-2026.06.21.r007-x86_64-1.txz b/dist/urbm-2026.06.21.r008-x86_64-1.txz similarity index 55% rename from dist/urbm-2026.06.21.r007-x86_64-1.txz rename to dist/urbm-2026.06.21.r008-x86_64-1.txz index 879fea4..683d44b 100644 Binary files a/dist/urbm-2026.06.21.r007-x86_64-1.txz and b/dist/urbm-2026.06.21.r008-x86_64-1.txz differ diff --git a/dist/urbm-2026.06.21.r008-x86_64-1.txz.sha256 b/dist/urbm-2026.06.21.r008-x86_64-1.txz.sha256 new file mode 100644 index 0000000..20b220c --- /dev/null +++ b/dist/urbm-2026.06.21.r008-x86_64-1.txz.sha256 @@ -0,0 +1 @@ +b571e6ee3cca59d6f63e15c721d53d04f52d27e4e121fa1d314dde7fe7d454cc urbm-2026.06.21.r008-x86_64-1.txz diff --git a/dist/urbm.plg b/dist/urbm.plg index d1ea1b9..b523ee2 100644 --- a/dist/urbm.plg +++ b/dist/urbm.plg @@ -2,13 +2,17 @@ - + - + ]> +### 2026.06.21.r008 +- Improve the visual hierarchy of folder trees with clearer disclosure buttons, row spacing, and normal path casing. +- Preserve tree and page scroll positions when expanding folders in directory and snapshot browsers. + ### 2026.06.21.r007 - Show an indeterminate progress bar while a complete snapshot file tree is loading. - Explain that no real percentage is available because Restic returns the full file listing only after completion. diff --git a/plugin/urbm.plg b/plugin/urbm.plg index 474afa8..28924e5 100644 --- a/plugin/urbm.plg +++ b/plugin/urbm.plg @@ -2,13 +2,17 @@ - + ]> +### 2026.06.21.r008 +- Improve the visual hierarchy of folder trees with clearer disclosure buttons, row spacing, and normal path casing. +- Preserve tree and page scroll positions when expanding folders in directory and snapshot browsers. + ### 2026.06.21.r007 - Show an indeterminate progress bar while a complete snapshot file tree is loading. - Explain that no real percentage is available because Restic returns the full file listing only after completion. diff --git a/webgui/URBM.page b/webgui/URBM.page index cfdf8c9..eb94dd4 100644 --- a/webgui/URBM.page +++ b/webgui/URBM.page @@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore" - +
- -

URBM 2026.06.21.r007

Restic Backup Manager für Unraid

+ +

URBM 2026.06.21.r008

Restic Backup Manager für Unraid

Verbindung wird hergestellt...
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
- + diff --git a/webgui/assets/urbm.css b/webgui/assets/urbm.css index e1d40f9..7f308a9 100644 --- a/webgui/assets/urbm.css +++ b/webgui/assets/urbm.css @@ -84,18 +84,20 @@ .bu-source-partial .bu-source-select { accent-color: var(--bu-accent); } .bu-partial-label { border: 1px solid var(--bu-accent); border-radius: 999px; color: var(--bu-text); display: inline-block; font-size: 11px; font-weight: 700; margin-left: 8px; padding: 2px 7px; vertical-align: middle; } .bu-inherited-label { border: 1px solid var(--bu-good); border-radius: 999px; color: var(--bu-good); display: inline-block; font-size: 11px; font-weight: 700; margin-left: 8px; padding: 2px 7px; vertical-align: middle; } -.bu-tree { border: 1px solid var(--bu-border); border-radius: 6px; max-height: 520px; overflow: auto; padding: 5px; } +.bu-tree { background: var(--bu-input); border: 1px solid var(--bu-border); border-radius: 6px; max-height: 520px; overflow: auto; padding: 6px; } .bu-tree-node { min-width: 0; } -.bu-tree-row { align-items: center; border-radius: 5px; display: grid; gap: 6px; grid-template-columns: 28px 24px minmax(0, 1fr); margin-left: calc(var(--bu-tree-depth) * 22px); min-height: 42px; padding: 3px 7px; } -.bu-dir-tree-row { grid-template-columns: 28px minmax(0, 1fr) auto; } +.bu-tree-node + .bu-tree-node { border-top: 1px solid color-mix(in srgb, var(--bu-border) 45%, transparent); } +.bu-tree-row { align-items: center; border-radius: 6px; display: grid; gap: 9px; grid-template-columns: 34px 24px minmax(0, 1fr); margin-left: calc(var(--bu-tree-depth) * 24px); min-height: 48px; padding: 5px 8px; } +.bu-dir-tree-row { grid-template-columns: 34px minmax(0, 1fr) auto; } .bu-tree-row:hover { background: var(--bu-panel-alt); } .bu-source-partial > .bu-tree-row { background: rgba(255, 140, 47, .09); background: color-mix(in srgb, var(--bu-accent) 9%, transparent); } #urbm-app .bu-tree-toggle, #urbm-app .bu-tree-name { appearance: none; background: transparent !important; border: 0 !important; box-shadow: none !important; color: var(--bu-text) !important; margin: 0 !important; min-height: 0; padding: 0 !important; text-align: left; text-shadow: none !important; } -#urbm-app .bu-tree-toggle { align-items: center; cursor: pointer; display: flex; height: 28px; justify-content: center; width: 28px; } -.bu-tree-toggle span { display: inline-block; font-size: 14px; transition: transform .16s ease; } +#urbm-app .bu-tree-toggle { align-items: center; background: var(--bu-panel-alt) !important; border: 1px solid var(--bu-border) !important; border-radius: 6px !important; cursor: pointer; display: flex; height: 30px; justify-content: center; width: 30px; } +#urbm-app .bu-tree-toggle:hover { border-color: var(--bu-accent) !important; } +.bu-tree-toggle span { display: inline-block; font-size: 13px; line-height: 1; transition: transform .16s ease; } .bu-tree-toggle.expanded span { transform: rotate(90deg); } -#urbm-app .bu-tree-name { cursor: pointer; display: grid; gap: 1px; grid-template-columns: auto minmax(0, 1fr); min-width: 0; } -.bu-tree-name > span:first-child { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +#urbm-app .bu-tree-name { cursor: pointer; display: grid; gap: 2px; grid-template-columns: auto minmax(0, 1fr); letter-spacing: normal !important; min-width: 0; text-transform: none !important; } +.bu-tree-name > span:first-child { font-weight: 700; letter-spacing: normal; overflow: hidden; text-overflow: ellipsis; text-transform: none; white-space: nowrap; } .bu-tree-name small { color: var(--bu-muted); grid-column: 1 / -1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .bu-tree-children { position: relative; } .bu-tree-children::before { background: var(--bu-border); bottom: 3px; content: ''; left: calc((var(--bu-tree-depth, 0) + 1) * 22px + 18px); opacity: .45; position: absolute; top: 0; width: 1px; } diff --git a/webgui/assets/urbm.js b/webgui/assets/urbm.js index a8de22a..19dd952 100644 --- a/webgui/assets/urbm.js +++ b/webgui/assets/urbm.js @@ -510,11 +510,15 @@ function renderSourceBrowser() { const host=document.getElementById('bu-source-browser'); if(!host) return; + const windowX=window.scrollX, windowY=window.scrollY; + const previousScroll=host.querySelector('.bu-tree')?.scrollTop || 0; const chosen=state.backupSelections.length?state.backupSelections.map(path=>`${esc(path)}`).join(''):'Keine Ordner ausgewählt.'; const tree=state.sourceTreeRoots.map(item=>sourceTreeNode(item,0)).join(''); host.innerHTML=`

Backup-Quellen auswählen

Ordner mit dem Dreieck aufklappen. Unterordner werden erst bei Bedarf geladen.
Ausgewählt: ${state.backupSelections.length}
${chosen}
${tree||'
Keine Ordner verfügbar.
'}
`; + const treeElement=host.querySelector('.bu-tree'); if(treeElement) treeElement.scrollTop=previousScroll; host.querySelectorAll('.bu-source-select[data-partial="true"]').forEach(box=>{box.indeterminate=true;box.setAttribute('aria-checked','mixed');}); enhanceTooltips(host); + window.scrollTo(windowX, windowY); } async function toggleSourceTree(path, forceOpen=false) { @@ -558,9 +562,13 @@ function renderDirectoryTree(key) { const tree=state.directoryTrees[key]; const host=document.getElementById(`bu-dir-tree-${key}`); if(!tree||!host) return; + const windowX=window.scrollX, windowY=window.scrollY; + const previousScroll=host.querySelector('.bu-tree')?.scrollTop || 0; const html=tree.roots.map(item=>directoryTreeNode(key,item,0)).join(''); host.innerHTML=`

${esc(tree.title)}

Ordner mit dem Dreieck aufklappen und den gewünschten Zielordner auswählen.
${html||'
Keine Ordner verfügbar.
'}
`; + const treeElement=host.querySelector('.bu-tree'); if(treeElement) treeElement.scrollTop=previousScroll; enhanceTooltips(host); + window.scrollTo(windowX, windowY); } async function toggleDirectoryTree(key, path, forceOpen=false) { @@ -738,6 +746,11 @@ notify('Konfiguration gespeichert'); return state.config; } + function renderPreservingScroll() { + const windowX=window.scrollX, windowY=window.scrollY; + render(); + window.scrollTo(windowX, windowY); + } const lines = (value) => value.split('\n').map(v=>v.trim()).filter(Boolean); const runTime = run => new Date(run.createdAt || run.startedAt || run.finishedAt || 0).getTime() || 0; const snapshotTime = snapshot => new Date(snapshot.time || 0).getTime() || 0; @@ -773,7 +786,7 @@ if (name === 'toggle-dir-tree') { return toggleDirectoryTree(a, decodeURIComponent(b)); } if (name === 'retry-dir-tree') { const path=decodeURIComponent(b); const tree=state.directoryTrees[a]; if(tree) { delete tree.children[path]; tree.errors[path]=''; } return toggleDirectoryTree(a,path,true); } if (name === 'select-dir-tree') { selectDirectoryTreePath(a, decodeURIComponent(b)); return; } - if (name === 'toggle-snapshot-tree') { const path=decodeURIComponent(a); state.snapshotTreeExpanded[path]=!state.snapshotTreeExpanded[path]; return render(); } + if (name === 'toggle-snapshot-tree') { const path=decodeURIComponent(a); state.snapshotTreeExpanded[path]=!state.snapshotTreeExpanded[path]; return renderPreservingScroll(); } if (name === 'restore-selected') { if(!state.restoreIncludes.length) throw new Error('Wähle mindestens eine Datei oder einen Ordner aus.'); state.view='restore'; document.querySelectorAll('.bu-tabs button').forEach(x=>x.classList.toggle('active',x.dataset.view==='restore')); return render(); } if (name === 'delete-job') { if(confirm('Diesen Job löschen?')) { state.config.jobs=state.config.jobs.filter(j=>j.id!==a); await saveConfig(); render(); } return; } if (name === 'duplicate-job') { const source=state.config.jobs.find(j=>j.id===a); const copy=structuredClone(source); copy.id=id('job'); copy.name += ' Kopie'; copy.enabled=false; state.config.jobs.push(copy); await saveConfig(); return render(); } @@ -879,7 +892,7 @@ if (e.target.classList.contains('bu-file-select')) { const path=e.target.value; const prefix=path.replace(/\/$/,'')+'/'; state.restoreIncludes = e.target.checked ? normalizeSourceSelections([...state.restoreIncludes.filter(selected=>!selected.startsWith(prefix)),path]) : state.restoreIncludes.filter(selected=>selected!==path); - render(); + renderPreservingScroll(); } if (e.target.classList.contains('bu-workload-select')) { state.workloadSelections = e.target.checked ? [...new Set([...state.workloadSelections,e.target.value])] : state.workloadSelections.filter(id=>id!==e.target.value);