diff --git a/dist/urbm-2026.06.21.r005-x86_64-1.txz.sha256 b/dist/urbm-2026.06.21.r005-x86_64-1.txz.sha256 deleted file mode 100644 index f899fbf..0000000 --- a/dist/urbm-2026.06.21.r005-x86_64-1.txz.sha256 +++ /dev/null @@ -1 +0,0 @@ -dca4b39ea845617f7789967f880f874984a8d9be85ee94b2fbea821734bd7559 urbm-2026.06.21.r005-x86_64-1.txz diff --git a/dist/urbm-2026.06.21.r005-x86_64-1.txz b/dist/urbm-2026.06.21.r006-x86_64-1.txz similarity index 56% rename from dist/urbm-2026.06.21.r005-x86_64-1.txz rename to dist/urbm-2026.06.21.r006-x86_64-1.txz index 5e047ca..8af35c5 100644 Binary files a/dist/urbm-2026.06.21.r005-x86_64-1.txz and b/dist/urbm-2026.06.21.r006-x86_64-1.txz differ diff --git a/dist/urbm-2026.06.21.r006-x86_64-1.txz.sha256 b/dist/urbm-2026.06.21.r006-x86_64-1.txz.sha256 new file mode 100644 index 0000000..8f47f71 --- /dev/null +++ b/dist/urbm-2026.06.21.r006-x86_64-1.txz.sha256 @@ -0,0 +1 @@ +03a78ede9f43f0848f0ac688f436a51f5fcd01dbf5638298c6ece7e8b18a742a urbm-2026.06.21.r006-x86_64-1.txz diff --git a/dist/urbm.plg b/dist/urbm.plg index 0f78c49..1872987 100644 --- a/dist/urbm.plg +++ b/dist/urbm.plg @@ -2,13 +2,17 @@ - + - + ]> +### 2026.06.21.r006 +- Sort runs and snapshots newest-first so lists get older from top to bottom. +- Apply newest-first ordering consistently to dashboard activity, activity history, failed runs, and snapshot tables. + ### 2026.06.21.r005 - Reduce the snapshot path-count column width so the browse action sits directly next to it. diff --git a/plugin/urbm.plg b/plugin/urbm.plg index 0101990..17b52a0 100644 --- a/plugin/urbm.plg +++ b/plugin/urbm.plg @@ -2,13 +2,17 @@ - + ]> +### 2026.06.21.r006 +- Sort runs and snapshots newest-first so lists get older from top to bottom. +- Apply newest-first ordering consistently to dashboard activity, activity history, failed runs, and snapshot tables. + ### 2026.06.21.r005 - Reduce the snapshot path-count column width so the browse action sits directly next to it. diff --git a/webgui/URBM.page b/webgui/URBM.page index 15c0e82..5ac480c 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.r005

Restic Backup Manager für Unraid

+ +

URBM 2026.06.21.r006

Restic Backup Manager für Unraid

Verbindung wird hergestellt...
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
- + diff --git a/webgui/assets/urbm.js b/webgui/assets/urbm.js index 3e669cc..574d853 100644 --- a/webgui/assets/urbm.js +++ b/webgui/assets/urbm.js @@ -335,7 +335,7 @@ async function load() { try { const [config, runs, daemon] = await Promise.all([api('/v1/config'), api('/v1/runs'), api('/v1/health')]); - state.config = config; state.runs = runs; + state.config = config; state.runs = newestRuns(runs); health.textContent = `Daemon online (${daemon.version || 'unbekannt'})`; health.className = 'bu-health ok'; render(); loadRepositoryStats(); } catch (error) { health.textContent = 'Daemon nicht erreichbar'; health.className = 'bu-health bad'; @@ -370,8 +370,9 @@ function renderDashboard() { const active = state.runs.filter(r => ['queued','running','paused'].includes(r.status)).length; - const failures = state.runs.filter(r => r.status === 'failed').slice(-5); - const last = [...state.runs].reverse().slice(0, 8); + const orderedRuns = newestRuns(state.runs); + const failures = orderedRuns.filter(r => r.status === 'failed').slice(0, 5); + const last = orderedRuns.slice(0, 8); const stats=state.repositoryStats; const statsErrors=stats.filter(item=>item.error); const loading=state.repositoryStatsLoading?'
Repository-Werte werden geladen…
':statsErrors.length?`
${statsErrors.map(item=>`${esc(item.repositoryName)}: ${esc(displayMessage(item.error))}`).join('
')}
`:''; @@ -629,7 +630,7 @@ function renderSnapshots() { const options = state.config.repositories.map(r=>``).join(''); const pathCount = paths => `${Number((paths||[]).length).toLocaleString()} Pfad${(paths||[]).length===1?'':'e'}`; - const rows = state.snapshots.map(s=>`${esc(s.short_id||s.id)}${esc(new Date(s.time).toLocaleString())}${esc(s.hostname)}${pathCount(s.paths)}${button('Durchsuchen',`browse:${s.id}`)}`).join(''); + const rows = newestSnapshots(state.snapshots).map(s=>`${esc(s.short_id||s.id)}${esc(new Date(s.time).toLocaleString())}${esc(s.hostname)}${pathCount(s.paths)}${button('Durchsuchen',`browse:${s.id}`)}`).join(''); return `

Snapshots

${button('Laden','load-snapshots','primary')}
${rows?`
${rows}
SnapshotZeitpunktHostPfade
`:'
Repository auswählen und Snapshots laden.
'}
${state.selectedSnapshot ? renderFileBrowser() : ''}`; } @@ -690,7 +691,7 @@ return `

Wiederherstellen

${button('Wiederherstellung einreihen','submit-restore','primary')}
`; } - function renderActivity() { return `

Aktivitäten

Laufende Aufgaben werden automatisch alle zwei Sekunden aktualisiert.
${button('Aktualisieren','refresh','primary')}${button('Abgeschlossene Aktivitäten löschen','clear-activity','danger')}
${runsTable([...state.runs].reverse())}
`; } + function renderActivity() { return `

Aktivitäten

Laufende Aufgaben werden automatisch alle zwei Sekunden aktualisiert.
${button('Aktualisieren','refresh','primary')}${button('Abgeschlossene Aktivitäten löschen','clear-activity','danger')}
${runsTable(newestRuns(state.runs))}
`; } function progressView(run) { if(run.status==='queued') return 'Wartet in der Warteschlange'; if(run.taskType!=='backup'&&run.taskType!=='rsync'&&!run.progressPercent) return 'Kein Prozentwert verfügbar'; @@ -737,6 +738,10 @@ return state.config; } 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; + const newestRuns = runs => [...runs].sort((a,b)=>runTime(b)-runTime(a)); + const newestSnapshots = snapshots => [...snapshots].sort((a,b)=>snapshotTime(b)-snapshotTime(a)); async function handle(action, element) { const [name, a, b] = action.split(':'); @@ -760,7 +765,7 @@ if (name === 'force-unlock-repo') { if(confirm('NOTFALL-ENTSPERRUNG\n\nDiese Aktion entfernt ALLE Restic-Sperren dieses Repositorys.\n\nNur fortfahren, wenn wirklich kein Backup, Restore, Check oder Prune mehr läuft - auch nicht auf einem anderen Server, der dieses Repository nutzt. Bei einem laufenden Vorgang kann das Repository beschädigt oder der laufende Vorgang fehlschlagen.\n\nAlle Sperren jetzt entfernen?')) { await api(`/v1/repositories/${a}/force-unlock`,{method:'POST'}); notify('Alle Repository-Sperren wurden entfernt'); } return; } if (name === 'maint') { await api(`/v1/repositories/${b}/${a}`,{method:'POST'}); notify(`${a==='check'?'Prüfung':'Bereinigung'} wurde eingereiht`); return load(); } if (name === 'repo-snapshots') { state.view='snapshots'; document.querySelector('[data-view="snapshots"]').click(); render(); setTimeout(()=>{document.getElementById('bu-snapshot-repo').value=a; handle('load-snapshots');},0); return; } - if (name === 'load-snapshots') { const repo=document.getElementById('bu-snapshot-repo').value; state.snapshots=await api(`/v1/repositories/${repo}/snapshots`); state.snapshotRepo=repo; return render(); } + if (name === 'load-snapshots') { const repo=document.getElementById('bu-snapshot-repo').value; state.snapshots=newestSnapshots(await api(`/v1/repositories/${repo}/snapshots`)); state.snapshotRepo=repo; return render(); } if (name === 'browse') { state.selectedSnapshot=a; state.restoreIncludes=[]; state.files=[]; state.snapshotTree=null; state.snapshotTreeError=''; state.snapshotTreeExpanded={'/':true}; state.snapshotTreeLoading=true; render(); try { state.files=await api(`/v1/repositories/${state.snapshotRepo}/snapshots/${a}/files`); state.snapshotTree=buildSnapshotTree(state.files); } catch(error) { state.snapshotTreeError=`Snapshot-Struktur konnte nicht geladen werden: ${error.message}`; } finally { state.snapshotTreeLoading=false; return render(); } } if (name === 'toggle-source-tree') { return toggleSourceTree(decodeURIComponent(a)); } if (name === 'retry-source-tree') { const path=decodeURIComponent(a); delete state.sourceTreeChildren[path]; state.sourceTreeErrors[path]=''; return toggleSourceTree(path,true); } @@ -910,6 +915,6 @@ window.addEventListener('scroll', hideTooltip, true); window.addEventListener('resize', hideTooltip); enhanceTooltips(root); - setInterval(async()=>{ if(!state.config)return; try { state.runs=await api('/v1/runs'); if(['dashboard','activity'].includes(state.view))render(); }catch(_){ } },2000); + setInterval(async()=>{ if(!state.config)return; try { state.runs=newestRuns(await api('/v1/runs')); if(['dashboard','activity'].includes(state.view))render(); }catch(_){ } },2000); load(); })();