Release URBM 2026.06.15.r012
This commit is contained in:
@@ -1 +0,0 @@
|
||||
84579aa8c702a442494daa340631dc71ef485fa05bbbfdd2980a68611e226bb4 urbm-2026.06.15.r011-x86_64-1.txz
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
5503027a5ce2c20162e16786a1892d13046fa8002fca2f156fc13b2ef66bf89f urbm-2026.06.15.r012-x86_64-1.txz
|
||||
Vendored
+7
-2
@@ -2,13 +2,18 @@
|
||||
<!DOCTYPE PLUGIN [
|
||||
<!ENTITY name "urbm">
|
||||
<!ENTITY author "Michael Roll">
|
||||
<!ENTITY version "2026.06.15.r011">
|
||||
<!ENTITY version "2026.06.15.r012">
|
||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||
<!ENTITY packageSHA256 "84579aa8c702a442494daa340631dc71ef485fa05bbbfdd2980a68611e226bb4">
|
||||
<!ENTITY packageSHA256 "5503027a5ce2c20162e16786a1892d13046fa8002fca2f156fc13b2ef66bf89f">
|
||||
]>
|
||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
||||
<CHANGES>
|
||||
### 2026.06.15.r012
|
||||
- Allow multiple backup jobs to share one Restic repository while retaining per-job snapshot tags and retention policies.
|
||||
- Catch asynchronous form submission errors so save failures are shown in the WebGUI instead of only in the browser console.
|
||||
- Clarify shared-repository behavior in backup job help text.
|
||||
|
||||
### 2026.06.15.r011
|
||||
- Parse Restic repository statistics correctly when progress output precedes the JSON result.
|
||||
- Support compact and pretty-printed multi-line statistics JSON.
|
||||
|
||||
@@ -26,7 +26,6 @@ func ValidateConfig(c Config) error {
|
||||
repos[repo.ID] = repo
|
||||
}
|
||||
jobs := make(map[string]struct{}, len(c.Jobs))
|
||||
repositoryOwners := make(map[string]string, len(c.Jobs))
|
||||
for _, job := range c.Jobs {
|
||||
if err := ValidateJob(job); err != nil {
|
||||
return fmt.Errorf("job %q: %w", job.ID, err)
|
||||
@@ -38,10 +37,6 @@ func ValidateConfig(c Config) error {
|
||||
if _, exists := repos[job.RepositoryID]; !exists {
|
||||
return fmt.Errorf("job %q references unknown repository %q", job.ID, job.RepositoryID)
|
||||
}
|
||||
if owner, exists := repositoryOwners[job.RepositoryID]; exists {
|
||||
return fmt.Errorf("repository %q is already assigned to job %q", job.RepositoryID, owner)
|
||||
}
|
||||
repositoryOwners[job.RepositoryID] = job.ID
|
||||
}
|
||||
notifications := make(map[string]struct{}, len(c.Notifications))
|
||||
for _, target := range c.Notifications {
|
||||
|
||||
@@ -46,15 +46,15 @@ func TestRepositoryIsolation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryCannotBeSharedByJobs(t *testing.T) {
|
||||
func TestRepositoryCanBeSharedByJobs(t *testing.T) {
|
||||
c := DefaultConfig()
|
||||
c.Repositories = []Repository{{SchemaVersion: 1, ID: "repo", Name: "Local", Type: RepositoryLocal, Location: "/tmp/repo", PasswordRef: "password"}}
|
||||
base := Job{SchemaVersion: 1, Name: "Flash", Type: JobFlash, RepositoryID: "repo", Consistency: Consistency{Mode: "live"}, Compression: "auto"}
|
||||
base := Job{SchemaVersion: 1, Name: "Flash", Type: JobFlash, RepositoryID: "repo", Consistency: Consistency{Mode: "live"}, Compression: "auto", Retention: Retention{KeepWithinDays: 30}}
|
||||
first, second := base, base
|
||||
first.ID, second.ID = "first", "second"
|
||||
c.Jobs = []Job{first, second}
|
||||
if err := ValidateConfig(c); err == nil {
|
||||
t.Fatal("shared repository accepted")
|
||||
if err := ValidateConfig(c); err != nil {
|
||||
t.Fatalf("shared repository rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -2,13 +2,18 @@
|
||||
<!DOCTYPE PLUGIN [
|
||||
<!ENTITY name "urbm">
|
||||
<!ENTITY author "Michael Roll">
|
||||
<!ENTITY version "2026.06.15.r011">
|
||||
<!ENTITY version "2026.06.15.r012">
|
||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&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/URBM/issues" icon="urbm.png">
|
||||
<CHANGES>
|
||||
### 2026.06.15.r012
|
||||
- Allow multiple backup jobs to share one Restic repository while retaining per-job snapshot tags and retention policies.
|
||||
- Catch asynchronous form submission errors so save failures are shown in the WebGUI instead of only in the browser console.
|
||||
- Clarify shared-repository behavior in backup job help text.
|
||||
|
||||
### 2026.06.15.r011
|
||||
- Parse Restic repository statistics correctly when progress output precedes the JSON result.
|
||||
- Support compact and pretty-printed multi-line statistics JSON.
|
||||
|
||||
+4
-4
@@ -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=20260615r011">
|
||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r012">
|
||||
<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=20260615r011" alt="URBM-Logo">
|
||||
<div><h1>URBM <span class="bu-version">2026.06.15.r011</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
||||
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r012" alt="URBM-Logo">
|
||||
<div><h1>URBM <span class="bu-version">2026.06.15.r012</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.r011.js"></script>
|
||||
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r012.js"></script>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
const fieldHelp = {
|
||||
name: 'Frei wählbarer Anzeigename. Beispiel: Appdata täglich oder VM Home Assistant.',
|
||||
type: 'Art der zu sichernden Daten. Beispiel: appdata für Containerdaten, vm für virtuelle Maschinen oder flash für den Unraid-USB-Stick.',
|
||||
repositoryId: 'Speicherziel für diesen Vorgang. Jeder Backup-Job verwendet ein eigenes Restic-Repository.',
|
||||
repositoryId: 'Speicherziel für diesen Vorgang. Mehrere Backup-Jobs können dasselbe Restic-Repository verwenden; Aufbewahrungsregeln bleiben über Job-Tags getrennt.',
|
||||
scheduleMode: 'Legt fest, an welchen Tagen das Backup startet. Beispiel: täglich, Montag bis Freitag oder ausgewählte Wochentage.',
|
||||
scheduleTime: 'Uhrzeit für den automatischen Start. Beispiel: 02:30 startet das Backup nachts um halb drei.',
|
||||
scheduleDays: 'Wähle mindestens einen Wochentag. Beispiel: Montag, Mittwoch und Sonntag jeweils zur eingestellten Uhrzeit.',
|
||||
@@ -251,7 +251,7 @@
|
||||
});
|
||||
const dashboardHelp = {
|
||||
'Aktive Jobs': 'Anzahl aktivierter Backup-Jobs, die nach Zeitplan laufen dürfen. Beispiel: 3 bedeutet drei aktive Sicherungspläne.',
|
||||
'Repositorys': 'Anzahl konfigurierter Restic-Speicherziele. Im MVP gehört jedes Repository genau zu einem Job.',
|
||||
'Repositorys': 'Anzahl konfigurierter Restic-Speicherziele. Mehrere Jobs dürfen dasselbe Repository verwenden.',
|
||||
'Wartend / laufend': 'Aufgaben in der Warteschlange oder in Ausführung. URBM führt immer nur eine Aufgabe gleichzeitig aus.',
|
||||
'Letzte Fehler': 'Fehlgeschlagene Läufe in der jüngsten Historie. Details stehen unter Aktivitäten.',
|
||||
};
|
||||
@@ -352,7 +352,7 @@
|
||||
|
||||
function renderJobs() {
|
||||
const rows = state.config.jobs.map(j => `<tr><td><strong>${esc(j.name)}</strong><br><span class="bu-muted">${esc(j.id)}</span></td><td>${esc(typeLabels[j.type]||j.type)}</td><td>${esc(repoName(j.repositoryId))}</td><td>${esc(scheduleLabel(j.schedule?.cron))}</td><td>${j.enabled ? status('success') : status('cancelled')}</td><td>${actionGroup(button('Starten',`run-job:${j.id}`,'primary')+button('Bearbeiten',`edit-job:${j.id}`)+button('Duplizieren',`duplicate-job:${j.id}`)+button('Löschen',`delete-job:${j.id}`,'danger'))}</td></tr>`).join('');
|
||||
return `<div class="bu-toolbar"><div><h2>Backup-Jobs</h2><div class="bu-muted">Jeder Job verwendet ein eigenes Restic-Repository.</div></div>${button('Neuer Job','new-job','primary')}</div>
|
||||
return `<div class="bu-toolbar"><div><h2>Backup-Jobs</h2><div class="bu-muted">Mehrere Jobs können dasselbe Restic-Repository verwenden; Snapshots und Aufbewahrung werden per Job-Tag getrennt.</div></div>${button('Neuer Job','new-job','primary')}</div>
|
||||
<section class="bu-card">${rows ? `<table class="bu-table"><thead><tr><th>Job</th><th>Typ</th><th>Repository</th><th>Zeitplan</th><th>Aktiv</th><th>Aktionen</th></tr></thead><tbody>${rows}</tbody></table>` : '<div class="bu-empty">Keine Backup-Jobs konfiguriert.</div>'}</section>`;
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@
|
||||
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(); }
|
||||
if (name === 'delete-repo') { if(state.config.jobs.some(j=>j.repositoryId===a)) throw new Error('Das Repository wird noch von einem Job verwendet.'); if(confirm('Dieses Repository aus der URBM-Konfiguration löschen? Die Backup-Daten am Ziel bleiben erhalten.')) { state.config.repositories=state.config.repositories.filter(r=>r.id!==a); await saveConfig(); render(); } return; }
|
||||
if (name.startsWith('submit-')) return submit(name);
|
||||
if (name.startsWith('submit-')) { await submit(name); return; }
|
||||
} catch (error) { notify(error.message, true); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user