Add safe stale repository unlock action

This commit is contained in:
Mikei386
2026-06-15 00:25:27 +02:00
parent 054fde24d3
commit e23563afb1
11 changed files with 67 additions and 9 deletions
+4 -4
View File
@@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
<?php
$pluginRoot = '/plugins/urbm';
?>
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r001">
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r002">
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php">
<header class="bu-header">
<div class="bu-brand">
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r001" alt="URBM-Logo">
<div><h1>URBM <span class="bu-version">2026.06.15.r001</span></h1><p>Restic Backup Manager für Unraid</p></div>
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r002" alt="URBM-Logo">
<div><h1>URBM <span class="bu-version">2026.06.15.r002</span></h1><p>Restic Backup Manager für Unraid</p></div>
</div>
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die URBM-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Verbindung wird hergestellt...</div>
</header>
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
</div>
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r001.js"></script>
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r002.js"></script>
+4 -1
View File
@@ -58,6 +58,7 @@
'new-repo': 'Legt ein neues, isoliertes Restic-Speicherziel an.',
'submit-repo': 'Speichert Ziel und Zugangsdaten. Das Repository wird dadurch noch nicht automatisch initialisiert.',
'init-repo': 'Initialisiert am Ziel einmalig ein neues, leeres Restic-Repository. Bei einem vorhandenen Repository nur das ursprüngliche Passwort eintragen und Test verwenden.',
'unlock-repo': 'Entfernt ausschließlich von Restic als veraltet erkannte Repository-Sperren. Aktive Sperren werden nicht gelöscht.',
'load-snapshots': 'Lädt alle Sicherungsstände des ausgewählten Repositorys.',
'restore-selected': 'Übernimmt die markierten Dateien oder Ordner in den Wiederherstellungsassistenten.',
'submit-restore': 'Stellt die Wiederherstellung in die globale Warteschlange; Backups und Wiederherstellungen laufen nacheinander.',
@@ -129,6 +130,7 @@
'delete-job': 'Löscht die Job-Konfiguration. Vorhandene Restic-Snapshots im Repository bleiben erhalten.',
'test-repo': 'Prüft Erreichbarkeit, Passwort und Restic-Zugriff, ohne ein Backup zu starten.',
'init-repo': 'Erstellt einmalig ein neues Restic-Repository. Nicht erneut ausführen und nicht für vorhandene Restic-Repositories verwenden.',
'unlock-repo': 'Entfernt verwaiste Restic-Locks, etwa nach einem Neustart oder abgebrochenen Prozess. Aktive Locks bleiben erhalten.',
'repo-snapshots': 'Wechselt zu den Sicherungsständen dieses Repositorys.',
'maint': action.includes(':check:') ? 'Prüft Struktur und Integrität des Restic-Repositorys.' : 'Entfernt nach Retention nicht mehr benötigte Datenblöcke und gibt Speicher frei.',
'edit-repo': 'Öffnet Ziel-, Mount- und Zugangseinstellungen dieses Repositorys.',
@@ -408,7 +410,7 @@
function renderRepositories() {
const rows = state.config.repositories.map(r => {
const initialize = volatileLocation(r) ? disabledButton('Initialisieren','Dieses Ziel liegt im flüchtigen RAM. Zuerst über Bearbeiten einen dauerhaften Pfad wie /mnt/user/Backups/HomeServer eintragen.') : button('Initialisieren',`init-repo:${r.id}`,'primary');
const actions = initialize+button('Testen',`test-repo:${r.id}`)+button('Snapshots',`repo-snapshots:${r.id}`)+button('Prüfen',`maint:check:${r.id}`)+button('Bereinigen',`maint:prune:${r.id}`)+button('Bearbeiten',`edit-repo:${r.id}`)+button('Löschen',`delete-repo:${r.id}`,'danger');
const actions = initialize+button('Testen',`test-repo:${r.id}`)+button('Entsperren',`unlock-repo:${r.id}`)+button('Snapshots',`repo-snapshots:${r.id}`)+button('Prüfen',`maint:check:${r.id}`)+button('Bereinigen',`maint:prune:${r.id}`)+button('Bearbeiten',`edit-repo:${r.id}`)+button('Löschen',`delete-repo:${r.id}`,'danger');
return `<tr><td><strong>${esc(r.name)}</strong><br><span class="bu-muted">${esc(r.id)}</span></td><td>${esc(typeLabels[r.type]||r.type)}</td><td>${esc(r.location)}${volatileLocation(r)?'<span class="bu-warning">Flüchtiger Pfad: Die Daten gehen beim Neustart verloren. Verwende /mnt/user/...</span>':''}</td><td>${r.mount?.managed?'Durch URBM':'Direkt / extern'}</td><td>${actionGroup(actions)}</td></tr>`;
}).join('');
return `<div class="bu-toolbar"><div><h2>Repositorys</h2><div class="bu-muted">Lokale, SMB-, NFS- und SFTP-Ziele.</div></div>${button('Neues Repository','new-repo','primary')}</div><section class="bu-card">${rows ? `<table class="bu-table"><thead><tr><th>Name</th><th>Typ</th><th>Speicherort</th><th>Einbindung</th><th>Aktionen</th></tr></thead><tbody>${rows}</tbody></table>` : '<div class="bu-empty">Keine Repositorys konfiguriert.</div>'}</section>`;
@@ -547,6 +549,7 @@
if (name === 'clear-activity') { if(confirm('Alle abgeschlossenen Aktivitäten und deren gespeicherte Laufprotokolle löschen?\n\nLaufende und wartende Aufgaben bleiben erhalten.')) { const result=await api('/v1/runs',{method:'DELETE'}); notify(result.cleared?`${result.cleared} abgeschlossene Aktivitäten gelöscht`:'Keine abgeschlossenen Aktivitäten vorhanden'); return load(); } return; }
if (name === 'test-repo') { await api(`/v1/repositories/${a}/test`,{method:'POST'}); return notify('Repository-Verbindung erfolgreich getestet'); }
if (name === 'init-repo') { if(confirm('Hier ein NEUES Restic-Repository erstellen?\n\nNur fortfahren, wenn dieses Ziel noch kein Restic-Repository enthält. Bei einem vorhandenen Repository das ursprüngliche Passwort unter Bearbeiten eintragen und Testen verwenden.')) { await api(`/v1/repositories/${a}/init`,{method:'POST'}); notify('Repository erfolgreich initialisiert'); } return; }
if (name === 'unlock-repo') { if(confirm('Verwaiste Restic-Sperren dieses Repositorys entfernen?\n\nRestic entfernt dabei nur als veraltet erkannte Locks. Verwende diese Aktion nach einem Neustart oder abgebrochenen Restic-Prozess.')) { await api(`/v1/repositories/${a}/unlock`,{method:'POST'}); notify('Verwaiste 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(); }