Release URBM 2026.06.15.r009
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
aa07788cc61822162b8caccd91b5b83afabf6dd770e4800c180dc6ce2b94abe5 urbm-2026.06.15.r008-x86_64-1.txz
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
648afa0c0b1348302f882422c5a199e56ecdcdc0f817bc9a402c1a73549bbeea urbm-2026.06.15.r009-x86_64-1.txz
|
||||||
Vendored
+7
-2
@@ -2,13 +2,18 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.15.r008">
|
<!ENTITY version "2026.06.15.r009">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
<!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 packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "aa07788cc61822162b8caccd91b5b83afabf6dd770e4800c180dc6ce2b94abe5">
|
<!ENTITY packageSHA256 "648afa0c0b1348302f882422c5a199e56ecdcdc0f817bc9a402c1a73549bbeea">
|
||||||
]>
|
]>
|
||||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
<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>
|
<CHANGES>
|
||||||
|
### 2026.06.15.r009
|
||||||
|
- Add a per-backup-job CPU core limit using Restic's GOMAXPROCS setting.
|
||||||
|
- Apply the configured limit to normal backups and optional USB raw-image backups without affecting restore or maintenance tasks.
|
||||||
|
- Preserve existing behavior with 0 meaning all available CPU cores.
|
||||||
|
|
||||||
### 2026.06.15.r008
|
### 2026.06.15.r008
|
||||||
- Replace backup-run history graphs with actual per-repository Restic statistics.
|
- Replace backup-run history graphs with actual per-repository Restic statistics.
|
||||||
- Show deduplicated stored repository data and the total files referenced by all snapshots.
|
- Show deduplicated stored repository data and the total files referenced by all snapshots.
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type Job struct {
|
|||||||
Schedule Schedule `json:"schedule"`
|
Schedule Schedule `json:"schedule"`
|
||||||
Consistency Consistency `json:"consistency"`
|
Consistency Consistency `json:"consistency"`
|
||||||
Compression string `json:"compression"`
|
Compression string `json:"compression"`
|
||||||
|
CPUCores int `json:"cpuCores,omitempty"`
|
||||||
Excludes []string `json:"excludes,omitempty"`
|
Excludes []string `json:"excludes,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
FlashImage bool `json:"flashImage,omitempty"`
|
FlashImage bool `json:"flashImage,omitempty"`
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ func ValidateJob(j Job) error {
|
|||||||
if j.Compression != "auto" && j.Compression != "off" && j.Compression != "max" {
|
if j.Compression != "auto" && j.Compression != "off" && j.Compression != "max" {
|
||||||
return errors.New("compression must be auto, off, or max")
|
return errors.New("compression must be auto, off, or max")
|
||||||
}
|
}
|
||||||
|
if j.CPUCores < 0 || j.CPUCores > 256 {
|
||||||
|
return errors.New("cpuCores must be between 0 and 256")
|
||||||
|
}
|
||||||
if j.Consistency.Mode != "live" && j.Consistency.Mode != "stop" {
|
if j.Consistency.Mode != "live" && j.Consistency.Mode != "stop" {
|
||||||
return errors.New("consistency mode must be live or stop")
|
return errors.New("consistency mode must be live or stop")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,3 +79,15 @@ func TestRetentionRequiresAtLeastOneRule(t *testing.T) {
|
|||||||
t.Fatalf("age retention rejected: %v", err)
|
t.Fatalf("age retention rejected: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBackupCPUCoresRange(t *testing.T) {
|
||||||
|
job := Job{SchemaVersion: 1, ID: "job", Name: "Share", Type: JobShare, RepositoryID: "repo", Sources: []Source{{Path: "/mnt/user/data"}}, Consistency: Consistency{Mode: "live"}, Compression: "auto", Retention: Retention{KeepWithinDays: 30}}
|
||||||
|
job.CPUCores = 2
|
||||||
|
if err := ValidateJob(job); err != nil {
|
||||||
|
t.Fatalf("valid CPU limit rejected: %v", err)
|
||||||
|
}
|
||||||
|
job.CPUCores = 257
|
||||||
|
if err := ValidateJob(job); err == nil {
|
||||||
|
t.Fatal("excessive CPU limit accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func (r *Runner) Backup(ctx context.Context, repo model.Repository, job model.Jo
|
|||||||
}
|
}
|
||||||
args = append(args, sources...)
|
args = append(args, sources...)
|
||||||
var summary Summary
|
var summary Summary
|
||||||
err := r.run(ctx, repo, args, func(line []byte) {
|
err := r.runWithEnv(ctx, repo, args, func(line []byte) {
|
||||||
var status Progress
|
var status Progress
|
||||||
if json.Unmarshal(line, &status) == nil && status.MessageType == "status" {
|
if json.Unmarshal(line, &status) == nil && status.MessageType == "status" {
|
||||||
if progress != nil {
|
if progress != nil {
|
||||||
@@ -127,7 +127,7 @@ func (r *Runner) Backup(ctx context.Context, repo model.Repository, job model.Jo
|
|||||||
if json.Unmarshal(line, &candidate) == nil && candidate.MessageType == "summary" {
|
if json.Unmarshal(line, &candidate) == nil && candidate.MessageType == "summary" {
|
||||||
summary = candidate
|
summary = candidate
|
||||||
}
|
}
|
||||||
}, nil)
|
}, nil, backupEnvironment(job))
|
||||||
return summary, err
|
return summary, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ func (r *Runner) BackupImage(ctx context.Context, repo model.Repository, job mod
|
|||||||
args = append(args, "--tag", tag)
|
args = append(args, "--tag", tag)
|
||||||
}
|
}
|
||||||
var summary Summary
|
var summary Summary
|
||||||
err := r.runWithInput(ctx, repo, args, func(line []byte) {
|
err := r.runWithInputAndEnv(ctx, repo, args, func(line []byte) {
|
||||||
var status Progress
|
var status Progress
|
||||||
if json.Unmarshal(line, &status) == nil && status.MessageType == "status" {
|
if json.Unmarshal(line, &status) == nil && status.MessageType == "status" {
|
||||||
if progress != nil {
|
if progress != nil {
|
||||||
@@ -149,7 +149,7 @@ func (r *Runner) BackupImage(ctx context.Context, repo model.Repository, job mod
|
|||||||
if json.Unmarshal(line, &candidate) == nil && candidate.MessageType == "summary" {
|
if json.Unmarshal(line, &candidate) == nil && candidate.MessageType == "summary" {
|
||||||
summary = candidate
|
summary = candidate
|
||||||
}
|
}
|
||||||
}, nil, image)
|
}, nil, image, backupEnvironment(job))
|
||||||
return summary, err
|
return summary, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +245,14 @@ func (r *Runner) Restore(ctx context.Context, repo model.Repository, task model.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) run(ctx context.Context, repo model.Repository, args []string, onLine func([]byte), capture *[]byte) error {
|
func (r *Runner) run(ctx context.Context, repo model.Repository, args []string, onLine func([]byte), capture *[]byte) error {
|
||||||
return r.runWithInput(ctx, repo, args, onLine, capture, nil)
|
return r.runWithInputAndEnv(ctx, repo, args, onLine, capture, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) runWithInput(ctx context.Context, repo model.Repository, args []string, onLine func([]byte), capture *[]byte, input io.Reader) error {
|
func (r *Runner) runWithEnv(ctx context.Context, repo model.Repository, args []string, onLine func([]byte), capture *[]byte, environment []string) error {
|
||||||
|
return r.runWithInputAndEnv(ctx, repo, args, onLine, capture, nil, environment)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) runWithInputAndEnv(ctx context.Context, repo model.Repository, args []string, onLine func([]byte), capture *[]byte, input io.Reader, environment []string) error {
|
||||||
password, err := r.Secrets.Get(repo.PasswordRef)
|
password, err := r.Secrets.Get(repo.PasswordRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("authentication: load repository password: %w", err)
|
return fmt.Errorf("authentication: load repository password: %w", err)
|
||||||
@@ -319,7 +323,7 @@ func (r *Runner) runWithInput(ctx context.Context, repo model.Repository, args [
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cmd.Env = []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/root", "RESTIC_PASSWORD_FILE=" + passwordPath}
|
cmd.Env = append([]string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/root", "RESTIC_PASSWORD_FILE=" + passwordPath}, environment...)
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -367,6 +371,13 @@ func (r *Runner) runWithInput(ctx context.Context, repo model.Repository, args [
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func backupEnvironment(job model.Job) []string {
|
||||||
|
if job.CPUCores <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{"GOMAXPROCS=" + fmt.Sprint(job.CPUCores)}
|
||||||
|
}
|
||||||
|
|
||||||
func resticError(message string, commandErr error) error {
|
func resticError(message string, commandErr error) error {
|
||||||
lower := strings.ToLower(message)
|
lower := strings.ToLower(message)
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -51,6 +51,29 @@ func TestBackupUsesPasswordFileAndStructuredArguments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBackupLimitsCPUForThisJob(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
envPath := filepath.Join(dir, "gomaxprocs")
|
||||||
|
script := filepath.Join(dir, "restic")
|
||||||
|
body := fmt.Sprintf("#!/bin/sh\nprintf '%%s' \"$GOMAXPROCS\" > '%s'\nprintf '%%s\\n' '{\"message_type\":\"summary\",\"snapshot_id\":\"cpu123\"}'\n", envPath)
|
||||||
|
if err := os.WriteFile(script, []byte(body), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
runner := &Runner{Binary: script, RuntimeDir: dir, Secrets: fakeSecrets{"password": "secret"}}
|
||||||
|
repo := model.Repository{Type: model.RepositoryLocal, Location: "/repo", PasswordRef: "password"}
|
||||||
|
job := model.Job{ID: "job", Compression: "auto", CPUCores: 2}
|
||||||
|
if _, err := runner.Backup(context.Background(), repo, job, []string{"/source"}, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
value, err := os.ReadFile(envPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(value) != "2" {
|
||||||
|
t.Fatalf("GOMAXPROCS = %q", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBackupImageStreamsRawDeviceWithStableFilename(t *testing.T) {
|
func TestBackupImageStreamsRawDeviceWithStableFilename(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
argsPath := filepath.Join(dir, "args")
|
argsPath := filepath.Join(dir, "args")
|
||||||
|
|||||||
+6
-1
@@ -2,13 +2,18 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.15.r008">
|
<!ENTITY version "2026.06.15.r009">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
<!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 packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
<!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">
|
<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>
|
<CHANGES>
|
||||||
|
### 2026.06.15.r009
|
||||||
|
- Add a per-backup-job CPU core limit using Restic's GOMAXPROCS setting.
|
||||||
|
- Apply the configured limit to normal backups and optional USB raw-image backups without affecting restore or maintenance tasks.
|
||||||
|
- Preserve existing behavior with 0 meaning all available CPU cores.
|
||||||
|
|
||||||
### 2026.06.15.r008
|
### 2026.06.15.r008
|
||||||
- Replace backup-run history graphs with actual per-repository Restic statistics.
|
- Replace backup-run history graphs with actual per-repository Restic statistics.
|
||||||
- Show deduplicated stored repository data and the total files referenced by all snapshots.
|
- Show deduplicated stored repository data and the total files referenced by all snapshots.
|
||||||
|
|||||||
+4
-4
@@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
|
|||||||
<?php
|
<?php
|
||||||
$pluginRoot = '/plugins/urbm';
|
$pluginRoot = '/plugins/urbm';
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r008">
|
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r009">
|
||||||
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php">
|
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php">
|
||||||
<header class="bu-header">
|
<header class="bu-header">
|
||||||
<div class="bu-brand">
|
<div class="bu-brand">
|
||||||
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r008" alt="URBM-Logo">
|
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r009" alt="URBM-Logo">
|
||||||
<div><h1>URBM <span class="bu-version">2026.06.15.r008</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
<div><h1>URBM <span class="bu-version">2026.06.15.r009</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
||||||
</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>
|
<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>
|
</header>
|
||||||
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
|
|||||||
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
||||||
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r008.js"></script>
|
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r009.js"></script>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
scheduleDays: 'Wähle mindestens einen Wochentag. Beispiel: Montag, Mittwoch und Sonntag jeweils zur eingestellten Uhrzeit.',
|
scheduleDays: 'Wähle mindestens einen Wochentag. Beispiel: Montag, Mittwoch und Sonntag jeweils zur eingestellten Uhrzeit.',
|
||||||
consistency: 'Live sichert ohne Unterbrechung, kann aber nur crash-konsistent sein. Controlled stop beendet Container oder VM vor dem Backup und startet sie danach wieder.',
|
consistency: 'Live sichert ohne Unterbrechung, kann aber nur crash-konsistent sein. Controlled stop beendet Container oder VM vor dem Backup und startet sie danach wieder.',
|
||||||
compression: 'Restic-Kompression. auto ist empfohlen; off spart CPU; max spart eher Speicher, benötigt aber mehr CPU-Zeit.',
|
compression: 'Restic-Kompression. auto ist empfohlen; off spart CPU; max spart eher Speicher, benötigt aber mehr CPU-Zeit.',
|
||||||
|
cpuCores: 'Begrenzt diesen Backup-Lauf über GOMAXPROCS auf die angegebene Anzahl CPU-Kerne. 0 verwendet wie bisher alle verfügbaren Kerne.',
|
||||||
sources: 'Optionale manuelle Quellen, eine pro Zeile. Ordner wählst du einfacher über die Checkbox-Liste. Docker- oder VM-ID mit @ voranstellen, z. B. @HomeAssistant.',
|
sources: 'Optionale manuelle Quellen, eine pro Zeile. Ordner wählst du einfacher über die Checkbox-Liste. Docker- oder VM-ID mit @ voranstellen, z. B. @HomeAssistant.',
|
||||||
excludes: 'Muster für Dateien, die nicht gesichert werden. Ein Muster pro Zeile. Beispiele: *.tmp, cache/** oder /mnt/user/appdata/plex/Cache/**.',
|
excludes: 'Muster für Dateien, die nicht gesichert werden. Ein Muster pro Zeile. Beispiele: *.tmp, cache/** oder /mnt/user/appdata/plex/Cache/**.',
|
||||||
shutdownSecs: 'Maximale Wartezeit für einen kontrollierten Stopp. Beispiel: 120 wartet bis zu zwei Minuten; danach wird das Backup ohne Force-Stop abgebrochen.',
|
shutdownSecs: 'Maximale Wartezeit für einen kontrollierten Stopp. Beispiel: 120 wartet bis zu zwei Minuten; danach wird das Backup ohne Force-Stop abgebrochen.',
|
||||||
@@ -365,6 +366,7 @@
|
|||||||
<input type="hidden" name="id" value="${esc(job.id || '')}"><label>Name<input name="name" required value="${esc(job.name || '')}"></label>
|
<input type="hidden" name="id" value="${esc(job.id || '')}"><label>Name<input name="name" required value="${esc(job.name || '')}"></label>
|
||||||
<label>Typ<select name="type">${['share','appdata','docker','vm','flash'].map(v=>`<option value="${v}" ${v===job.type?'selected':''}>${esc(typeLabels[v])}</option>`).join('')}</select></label>
|
<label>Typ<select name="type">${['share','appdata','docker','vm','flash'].map(v=>`<option value="${v}" ${v===job.type?'selected':''}>${esc(typeLabels[v])}</option>`).join('')}</select></label>
|
||||||
<label>Repository<select name="repositoryId" required>${options}</select></label><label>Kompression<select name="compression"><option value="auto" ${(job.compression||'auto')==='auto'?'selected':''}>Automatisch</option><option value="off" ${job.compression==='off'?'selected':''}>Aus</option><option value="max" ${job.compression==='max'?'selected':''}>Maximal</option></select></label>
|
<label>Repository<select name="repositoryId" required>${options}</select></label><label>Kompression<select name="compression"><option value="auto" ${(job.compression||'auto')==='auto'?'selected':''}>Automatisch</option><option value="off" ${job.compression==='off'?'selected':''}>Aus</option><option value="max" ${job.compression==='max'?'selected':''}>Maximal</option></select></label>
|
||||||
|
<label>CPU-Kerne für dieses Backup<input name="cpuCores" type="number" min="0" max="256" value="${Number(job.cpuCores||0)}"><span class="bu-muted">0 = alle verfügbaren Kerne</span></label>
|
||||||
${scheduleFields(job)}
|
${scheduleFields(job)}
|
||||||
<div id="bu-job-specific" class="wide"></div>
|
<div id="bu-job-specific" class="wide"></div>
|
||||||
<label class="wide">Ausschlussmuster, eines pro Zeile<textarea name="excludes">${esc((job.excludes||[]).join('\n'))}</textarea></label>
|
<label class="wide">Ausschlussmuster, eines pro Zeile<textarea name="excludes">${esc((job.excludes||[]).join('\n'))}</textarea></label>
|
||||||
@@ -610,7 +612,7 @@
|
|||||||
else sources=[...state.backupSelections.map(path=>({path})),...lines(f.get('sources')||'').map(path=>({path}))].filter((item,index,all)=>all.findIndex(other=>other.path===item.path)===index);
|
else sources=[...state.backupSelections.map(path=>({path})),...lines(f.get('sources')||'').map(path=>({path}))].filter((item,index,all)=>all.findIndex(other=>other.path===item.path)===index);
|
||||||
const retention={keepWithinDays:Number(f.get('keepWithinDays')),daily:Number(f.get('keepDaily')),weekly:Number(f.get('keepWeekly')),monthly:Number(f.get('keepMonthly'))};
|
const retention={keepWithinDays:Number(f.get('keepWithinDays')),daily:Number(f.get('keepDaily')),weekly:Number(f.get('keepWeekly')),monthly:Number(f.get('keepMonthly'))};
|
||||||
if(!retention.keepWithinDays&&!retention.daily&&!retention.weekly&&!retention.monthly) throw new Error('Die Aufbewahrung benötigt mindestens eine Alters- oder Kalenderregel.');
|
if(!retention.keepWithinDays&&!retention.daily&&!retention.weekly&&!retention.monthly) throw new Error('Die Aufbewahrung benötigt mindestens eine Alters- oder Kalenderregel.');
|
||||||
const job={schemaVersion:1,id:f.get('id')||id('job'),name:f.get('name'),type,enabled:f.get('enabled')==='on',repositoryId:f.get('repositoryId'),sources,schedule:{cron:scheduleCron(f),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},consistency:{mode:f.get('consistency')||'live'},compression:f.get('compression'),excludes:lines(f.get('excludes')),tags:existing?.tags||[],flashImage:type==='flash'&&f.get('flashImage')==='on',retention,notifyOn:existing?.notifyOn||['success','warning','failed'],shutdownTimeoutSeconds:Number(f.get('shutdownSecs')||120)};
|
const job={schemaVersion:1,id:f.get('id')||id('job'),name:f.get('name'),type,enabled:f.get('enabled')==='on',repositoryId:f.get('repositoryId'),sources,schedule:{cron:scheduleCron(f),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},consistency:{mode:f.get('consistency')||'live'},compression:f.get('compression'),cpuCores:Number(f.get('cpuCores')||0),excludes:lines(f.get('excludes')),tags:existing?.tags||[],flashImage:type==='flash'&&f.get('flashImage')==='on',retention,notifyOn:existing?.notifyOn||['success','warning','failed'],shutdownTimeoutSeconds:Number(f.get('shutdownSecs')||120)};
|
||||||
state.config.jobs=state.config.jobs.filter(j=>j.id!==job.id).concat(job); await saveConfig(); render();
|
state.config.jobs=state.config.jobs.filter(j=>j.id!==job.id).concat(job); await saveConfig(); render();
|
||||||
}
|
}
|
||||||
if (kind === 'submit-repo') {
|
if (kind === 'submit-repo') {
|
||||||
|
|||||||
Reference in New Issue
Block a user