Add Restic read concurrency setting
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
ad334b4da853736a6583a0a96e11d3d196a4b22275b95dc0eb0e624a66f77bdb urbm-2026.06.15.r022-x86_64-1.txz
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
e7a53aae8a052d41025771b79811176c9570f6ac767734e8ef77fa24fcea0bca urbm-2026.06.15.r023-x86_64-1.txz
|
||||||
Vendored
+6
-2
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.15.r022">
|
<!ENTITY version "2026.06.15.r023">
|
||||||
<!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 "ad334b4da853736a6583a0a96e11d3d196a4b22275b95dc0eb0e624a66f77bdb">
|
<!ENTITY packageSHA256 "e7a53aae8a052d41025771b79811176c9570f6ac767734e8ef77fa24fcea0bca">
|
||||||
]>
|
]>
|
||||||
<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.r023
|
||||||
|
- Add a per-job Restic file read concurrency setting for faster backups from SSD and NVMe sources.
|
||||||
|
- Keep zero as the backward-compatible Restic default and validate custom values from 1 through 64.
|
||||||
|
|
||||||
### 2026.06.15.r022
|
### 2026.06.15.r022
|
||||||
- Replace the source folder table with a lazy-loaded expandable tree for Restic and Rsync jobs.
|
- Replace the source folder table with a lazy-loaded expandable tree for Restic and Rsync jobs.
|
||||||
- Add rotating disclosure triangles, nested indentation, and per-folder loading, empty, retry, and error states.
|
- Add rotating disclosure triangles, nested indentation, and per-folder loading, empty, retry, and error states.
|
||||||
|
|||||||
+19
-18
@@ -47,24 +47,25 @@ type Settings struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
SchemaVersion int `json:"schemaVersion"`
|
SchemaVersion int `json:"schemaVersion"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type JobType `json:"type"`
|
Type JobType `json:"type"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
RepositoryID string `json:"repositoryId"`
|
RepositoryID string `json:"repositoryId"`
|
||||||
Sources []Source `json:"sources"`
|
Sources []Source `json:"sources"`
|
||||||
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"`
|
CPUCores int `json:"cpuCores,omitempty"`
|
||||||
Excludes []string `json:"excludes,omitempty"`
|
ReadConcurrency int `json:"readConcurrency,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Excludes []string `json:"excludes,omitempty"`
|
||||||
FlashImage bool `json:"flashImage,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Retention Retention `json:"retention"`
|
FlashImage bool `json:"flashImage,omitempty"`
|
||||||
NotifyOn []string `json:"notifyOn,omitempty"`
|
Retention Retention `json:"retention"`
|
||||||
ShutdownSecs int `json:"shutdownTimeoutSeconds"`
|
NotifyOn []string `json:"notifyOn,omitempty"`
|
||||||
Rsync RsyncOptions `json:"rsync,omitempty"`
|
ShutdownSecs int `json:"shutdownTimeoutSeconds"`
|
||||||
|
Rsync RsyncOptions `json:"rsync,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RsyncOptions struct {
|
type RsyncOptions struct {
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ func ValidateJob(j Job) error {
|
|||||||
if j.CPUCores < 0 || j.CPUCores > 256 {
|
if j.CPUCores < 0 || j.CPUCores > 256 {
|
||||||
return errors.New("cpuCores must be between 0 and 256")
|
return errors.New("cpuCores must be between 0 and 256")
|
||||||
}
|
}
|
||||||
|
if j.ReadConcurrency < 0 || j.ReadConcurrency > 64 {
|
||||||
|
return errors.New("readConcurrency must be between 0 and 64")
|
||||||
|
}
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,18 @@ func TestBackupCPUCoresRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBackupReadConcurrencyRange(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.ReadConcurrency = 8
|
||||||
|
if err := ValidateJob(job); err != nil {
|
||||||
|
t.Fatalf("valid read concurrency rejected: %v", err)
|
||||||
|
}
|
||||||
|
job.ReadConcurrency = 65
|
||||||
|
if err := ValidateJob(job); err == nil {
|
||||||
|
t.Fatal("excessive read concurrency accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRsyncJobDoesNotRequireRepository(t *testing.T) {
|
func TestRsyncJobDoesNotRequireRepository(t *testing.T) {
|
||||||
c := DefaultConfig()
|
c := DefaultConfig()
|
||||||
c.Jobs = []Job{{SchemaVersion: 1, ID: "rsync-job", Name: "Mirror", Type: JobRsync, Enabled: true, Sources: []Source{{Path: "/mnt/user/data"}}, Rsync: RsyncOptions{Target: "/mnt/remotes/backup", Overwrite: true}}}
|
c.Jobs = []Job{{SchemaVersion: 1, ID: "rsync-job", Name: "Mirror", Type: JobRsync, Enabled: true, Sources: []Source{{Path: "/mnt/user/data"}}, Rsync: RsyncOptions{Target: "/mnt/remotes/backup", Overwrite: true}}}
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ func (r *Runner) TestPassword(ctx context.Context, repo model.Repository, passwo
|
|||||||
|
|
||||||
func (r *Runner) Backup(ctx context.Context, repo model.Repository, job model.Job, sources []string, progress ProgressCallback) (Summary, error) {
|
func (r *Runner) Backup(ctx context.Context, repo model.Repository, job model.Job, sources []string, progress ProgressCallback) (Summary, error) {
|
||||||
args := []string{"backup", "--json", "--compression", job.Compression}
|
args := []string{"backup", "--json", "--compression", job.Compression}
|
||||||
|
if job.ReadConcurrency > 0 {
|
||||||
|
args = append(args, "--read-concurrency", fmt.Sprint(job.ReadConcurrency))
|
||||||
|
}
|
||||||
for _, tag := range append([]string{"urbm", "job:" + job.ID}, job.Tags...) {
|
for _, tag := range append([]string{"urbm", "job:" + job.ID}, job.Tags...) {
|
||||||
args = append(args, "--tag", tag)
|
args = append(args, "--tag", tag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,29 @@ func TestBackupLimitsCPUForThisJob(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBackupSetsReadConcurrencyForThisJob(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
argsPath := filepath.Join(dir, "args")
|
||||||
|
script := filepath.Join(dir, "restic")
|
||||||
|
body := fmt.Sprintf("#!/bin/sh\nprintf '%%s\\n' \"$@\" > '%s'\nprintf '%%s\\n' '{\"message_type\":\"summary\",\"snapshot_id\":\"read123\"}'\n", argsPath)
|
||||||
|
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", ReadConcurrency: 8}
|
||||||
|
if _, err := runner.Backup(context.Background(), repo, job, []string{"/source"}, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
args, err := os.ReadFile(argsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(args), "--read-concurrency\n8\n") {
|
||||||
|
t.Fatalf("read concurrency missing from arguments: %s", args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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")
|
||||||
|
|||||||
+5
-1
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.15.r022">
|
<!ENTITY version "2026.06.15.r023">
|
||||||
<!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.r023
|
||||||
|
- Add a per-job Restic file read concurrency setting for faster backups from SSD and NVMe sources.
|
||||||
|
- Keep zero as the backward-compatible Restic default and validate custom values from 1 through 64.
|
||||||
|
|
||||||
### 2026.06.15.r022
|
### 2026.06.15.r022
|
||||||
- Replace the source folder table with a lazy-loaded expandable tree for Restic and Rsync jobs.
|
- Replace the source folder table with a lazy-loaded expandable tree for Restic and Rsync jobs.
|
||||||
- Add rotating disclosure triangles, nested indentation, and per-folder loading, empty, retry, and error states.
|
- Add rotating disclosure triangles, nested indentation, and per-folder loading, empty, retry, and error states.
|
||||||
|
|||||||
+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=20260615r022">
|
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r023">
|
||||||
<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=20260615r022" alt="URBM-Logo">
|
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r023" alt="URBM-Logo">
|
||||||
<div><h1>URBM <span class="bu-version">2026.06.15.r022</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
<div><h1>URBM <span class="bu-version">2026.06.15.r023</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.r022.js"></script>
|
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r023.js"></script>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
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.',
|
cpuCores: 'Begrenzt diesen Backup-Lauf über GOMAXPROCS auf die angegebene Anzahl CPU-Kerne. 0 verwendet wie bisher alle verfügbaren Kerne.',
|
||||||
|
readConcurrency: 'Anzahl der Dateien, die Restic gleichzeitig liest. Höhere Werte können SSD- und NVMe-Quellen mit vielen kleinen Dateien beschleunigen, bei HDDs aber langsamer sein. 0 verwendet den Restic-Standard.',
|
||||||
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.',
|
||||||
@@ -388,7 +389,7 @@
|
|||||||
content.innerHTML = `<section class="bu-card"><h2>Backup-Job ${job.id ? 'bearbeiten' : 'erstellen'}</h2><form id="bu-job-form" class="bu-form">
|
content.innerHTML = `<section class="bu-card"><h2>Backup-Job ${job.id ? 'bearbeiten' : 'erstellen'}</h2><form id="bu-job-form" class="bu-form">
|
||||||
<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','rsync'].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','rsync'].map(v=>`<option value="${v}" ${v===job.type?'selected':''}>${esc(typeLabels[v])}</option>`).join('')}</select></label>
|
||||||
<fieldset id="bu-restic-job-settings" class="wide bu-fieldset"><legend>Restic-Einstellungen</legend><div class="bu-form-grid"><label>Repository<select name="repositoryId">${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></div></fieldset>
|
<fieldset id="bu-restic-job-settings" class="wide bu-fieldset"><legend>Restic-Einstellungen</legend><div class="bu-form-grid"><label>Repository<select name="repositoryId">${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><label>Gleichzeitig gelesene Dateien<input name="readConcurrency" type="number" min="0" max="64" value="${Number(job.readConcurrency||0)}"><span class="bu-muted">0 = Restic-Standard</span></label></div></fieldset>
|
||||||
${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>
|
||||||
@@ -735,7 +736,7 @@
|
|||||||
if(type==='rsync'&&!rsync.target) throw new Error('Wähle einen Zielordner für die Rsync-Kopie.');
|
if(type==='rsync'&&!rsync.target) throw new Error('Wähle einen Zielordner für die Rsync-Kopie.');
|
||||||
if(type==='rsync'&&rsync.delete&&!confirm('Rsync darf Dateien am Ziel löschen, die in der Quelle nicht mehr vorhanden sind. Diese Option kann Daten am Ziel entfernen. Wirklich speichern?')) return;
|
if(type==='rsync'&&rsync.delete&&!confirm('Rsync darf Dateien am Ziel löschen, die in der Quelle nicht mehr vorhanden sind. Diese Option kann Daten am Ziel entfernen. Wirklich speichern?')) return;
|
||||||
if(type==='rsync'&&rsync.skipSpaceCheck&&!confirm('Die Kapazitätsprüfung wirklich abschalten? Der Rsync-Lauf kann dann das Ziel vollständig füllen und mit einem Schreibfehler abbrechen.')) return;
|
if(type==='rsync'&&rsync.skipSpaceCheck&&!confirm('Die Kapazitätsprüfung wirklich abschalten? Der Rsync-Lauf kann dann das Ziel vollständig füllen und mit einem Schreibfehler abbrechen.')) return;
|
||||||
const job={schemaVersion:1,id:f.get('id')||id('job'),name:f.get('name'),type,enabled:f.get('enabled')==='on',repositoryId:type==='rsync'?'':f.get('repositoryId'),sources,schedule:{cron:scheduleCron(f),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},consistency:{mode:f.get('consistency')||'live'},compression:type==='rsync'?'':f.get('compression'),cpuCores:type==='rsync'?0:Number(f.get('cpuCores')||0),excludes:lines(f.get('excludes')),tags:existing?.tags||[],flashImage:type==='flash'&&f.get('flashImage')==='on',retention:type==='rsync'?{}:retention,rsync,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:type==='rsync'?'':f.get('repositoryId'),sources,schedule:{cron:scheduleCron(f),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},consistency:{mode:f.get('consistency')||'live'},compression:type==='rsync'?'':f.get('compression'),cpuCores:type==='rsync'?0:Number(f.get('cpuCores')||0),readConcurrency:type==='rsync'?0:Number(f.get('readConcurrency')||0),excludes:lines(f.get('excludes')),tags:existing?.tags||[],flashImage:type==='flash'&&f.get('flashImage')==='on',retention:type==='rsync'?{}:retention,rsync,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