Remove automatic repository subdirectories
This commit is contained in:
@@ -1 +0,0 @@
|
||||
83eb2bd7c3c60ca5b973755355b016262a5b7020d18eb45bf4aa000c1f533053 urbm-2026.06.14.r004-x86_64-1.txz
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
0d947c4dd0796e1ae0a48aa4f0199d1581f338fe093116879028a7f71f4a12cd urbm-2026.06.14.r005-x86_64-1.txz
|
||||
Vendored
+4
-5
@@ -2,16 +2,15 @@
|
||||
<!DOCTYPE PLUGIN [
|
||||
<!ENTITY name "urbm">
|
||||
<!ENTITY author "Michael Roll">
|
||||
<!ENTITY version "2026.06.14.r004">
|
||||
<!ENTITY version "2026.06.14.r005">
|
||||
<!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 "83eb2bd7c3c60ca5b973755355b016262a5b7020d18eb45bf4aa000c1f533053">
|
||||
<!ENTITY packageSHA256 "0d947c4dd0796e1ae0a48aa4f0199d1581f338fe093116879028a7f71f4a12cd">
|
||||
]>
|
||||
<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.14.r004
|
||||
- Create a dedicated repository subdirectory from the repository name for every newly configured local, SMB, NFS, or SFTP destination.
|
||||
- Preserve existing repository locations and safely separate managed network mount points from their repository subdirectories.
|
||||
### 2026.06.14.r005
|
||||
- Remove automatic repository subdirectory creation. The selected or entered path is used directly as the Restic repository location.
|
||||
|
||||
### 2026.06.14.r003
|
||||
- Use URBM consistently as the Unraid navigation and page name instead of the translated Backup / Sicherung labels.
|
||||
|
||||
@@ -166,12 +166,6 @@ func ValidateRepository(r Repository) error {
|
||||
if r.Mount.Remote == "" || !filepath.IsAbs(r.Mount.MountPoint) {
|
||||
return errors.New("managed mount requires remote and absolute mountPoint")
|
||||
}
|
||||
location := filepath.Clean(r.Location)
|
||||
mountPoint := filepath.Clean(r.Mount.MountPoint)
|
||||
relative, err := filepath.Rel(mountPoint, location)
|
||||
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
||||
return errors.New("managed repository location must be inside mountPoint")
|
||||
}
|
||||
}
|
||||
if r.Type == RepositorySFTP && r.CredentialRef != "" {
|
||||
knownHosts := r.Options["knownHostsPath"]
|
||||
|
||||
@@ -79,14 +79,3 @@ func TestRetentionRequiresAtLeastOneRule(t *testing.T) {
|
||||
t.Fatalf("age retention rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagedRepositoryLocationMustRemainInsideMountPoint(t *testing.T) {
|
||||
repo := Repository{SchemaVersion: 1, ID: "repo", Name: "NAS", Type: RepositorySMB, Location: "/mnt/remotes/nas/NAS", PasswordRef: "password", Mount: &MountConfig{Managed: true, Remote: "//nas/backups", MountPoint: "/mnt/remotes/nas"}}
|
||||
if err := ValidateRepository(repo); err != nil {
|
||||
t.Fatalf("repository subdirectory rejected: %v", err)
|
||||
}
|
||||
repo.Location = "/mnt/remotes/other/NAS"
|
||||
if err := ValidateRepository(repo); err == nil {
|
||||
t.Fatal("repository location outside mountPoint accepted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ type MountManager struct {
|
||||
type MountedRepository struct {
|
||||
Repository model.Repository
|
||||
Mounted bool
|
||||
MountPoint string
|
||||
CredentialFile string
|
||||
}
|
||||
|
||||
@@ -33,24 +32,14 @@ func (m *MountManager) Prepare(ctx context.Context, repo model.Repository) (Moun
|
||||
}
|
||||
if repo.Mount == nil || !repo.Mount.Managed {
|
||||
if _, err := os.Stat(repo.Location); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return result, fmt.Errorf("connectivity: external mount unavailable: %w", err)
|
||||
}
|
||||
if _, parentErr := os.Stat(filepath.Dir(repo.Location)); parentErr != nil {
|
||||
return result, fmt.Errorf("connectivity: external mount unavailable: %w", parentErr)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
point := filepath.Clean(repo.Mount.MountPoint)
|
||||
if !strings.HasPrefix(point, "/mnt/remotes/") && !strings.HasPrefix(point, "/mnt/disks/") {
|
||||
return result, errors.New("validation: managed mountPoint must be below /mnt/remotes or /mnt/disks")
|
||||
}
|
||||
repositoryPath := filepath.Clean(repo.Location)
|
||||
relative, err := filepath.Rel(point, repositoryPath)
|
||||
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
||||
return result, errors.New("validation: managed repository location must be inside its mountPoint")
|
||||
}
|
||||
if err := os.MkdirAll(point, 0700); err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -97,7 +86,7 @@ func (m *MountManager) Prepare(ctx context.Context, repo model.Repository) (Moun
|
||||
return result, fmt.Errorf("connectivity: mount repository: %w", err)
|
||||
}
|
||||
result.Mounted = true
|
||||
result.MountPoint = point
|
||||
result.Repository.Location = point
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -108,5 +97,5 @@ func (m *MountManager) Cleanup(ctx context.Context, mounted MountedRepository) e
|
||||
if !mounted.Mounted {
|
||||
return nil
|
||||
}
|
||||
return exec.CommandContext(ctx, "umount", mounted.MountPoint).Run()
|
||||
return exec.CommandContext(ctx, "umount", mounted.Repository.Location).Run()
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.casaderoll.de/michael/urbm/internal/model"
|
||||
)
|
||||
|
||||
func TestExternalMountAllowsNewRepositorySubdirectory(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
repo := model.Repository{Type: model.RepositorySMB, Location: filepath.Join(base, "Photos")}
|
||||
manager := &MountManager{}
|
||||
|
||||
mounted, err := manager.Prepare(context.Background(), repo)
|
||||
if err != nil {
|
||||
t.Fatalf("new repository subdirectory rejected: %v", err)
|
||||
}
|
||||
if mounted.Repository.Location != repo.Location {
|
||||
t.Fatalf("location = %q, want %q", mounted.Repository.Location, repo.Location)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalMountRejectsMissingBaseDirectory(t *testing.T) {
|
||||
repo := model.Repository{Type: model.RepositoryNFS, Location: filepath.Join(t.TempDir(), "missing", "Photos")}
|
||||
manager := &MountManager{}
|
||||
|
||||
if _, err := manager.Prepare(context.Background(), repo); err == nil {
|
||||
t.Fatal("missing external mount base accepted")
|
||||
}
|
||||
}
|
||||
@@ -226,11 +226,6 @@ func (s *Service) TestRepository(ctx context.Context, repoID string, initialize
|
||||
}
|
||||
defer s.mounts.Cleanup(context.Background(), mounted)
|
||||
if initialize {
|
||||
if mounted.Repository.Type != model.RepositorySFTP {
|
||||
if err := os.MkdirAll(mounted.Repository.Location, 0750); err != nil {
|
||||
return fmt.Errorf("repository: create destination directory: %w", err)
|
||||
}
|
||||
}
|
||||
return s.restic.Init(ctx, mounted.Repository)
|
||||
}
|
||||
_, err = s.restic.Snapshots(ctx, mounted.Repository)
|
||||
|
||||
+3
-4
@@ -2,16 +2,15 @@
|
||||
<!DOCTYPE PLUGIN [
|
||||
<!ENTITY name "urbm">
|
||||
<!ENTITY author "Michael Roll">
|
||||
<!ENTITY version "2026.06.14.r004">
|
||||
<!ENTITY version "2026.06.14.r005">
|
||||
<!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.14.r004
|
||||
- Create a dedicated repository subdirectory from the repository name for every newly configured local, SMB, NFS, or SFTP destination.
|
||||
- Preserve existing repository locations and safely separate managed network mount points from their repository subdirectories.
|
||||
### 2026.06.14.r005
|
||||
- Remove automatic repository subdirectory creation. The selected or entered path is used directly as the Restic repository location.
|
||||
|
||||
### 2026.06.14.r003
|
||||
- Use URBM consistently as the Unraid navigation and page name instead of the translated Backup / Sicherung labels.
|
||||
|
||||
+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=20260614r004">
|
||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260614r005">
|
||||
<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=20260614r004" alt="URBM-Logo">
|
||||
<div><h1>URBM <span class="bu-version">2026.06.14.r004</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
||||
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260614r005" alt="URBM-Logo">
|
||||
<div><h1>URBM <span class="bu-version">2026.06.14.r005</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.14.r004.js"></script>
|
||||
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.14.r005.js"></script>
|
||||
|
||||
+6
-33
@@ -27,7 +27,7 @@
|
||||
keepWeekly: 'Zusätzliche wöchentliche Stände außerhalb der Altersfrist. 0 deaktiviert diese Zusatzregel.',
|
||||
keepMonthly: 'Zusätzliche monatliche Stände außerhalb der Altersfrist. 0 deaktiviert diese Zusatzregel.',
|
||||
enabled: 'Aktiviert automatische geplante Läufe. Deaktivierte Jobs können weiterhin gespeichert, aber nicht automatisch ausgeführt werden.',
|
||||
location: 'Bei neuen Repositorys ist dies der Basisordner. URBM ergänzt automatisch einen sicheren Unterordner aus dem Repository-Namen. Beispiel: /mnt/user/Backups und Fotos Zuhause ergeben /mnt/user/Backups/Fotos-Zuhause.',
|
||||
location: 'Restic-Ziel. Lokal: /mnt/user/backups/job1. SFTP: user@server:/backup/job1. Bei SMB/NFS mit Mount wird hier der Mount-Pfad verwendet.',
|
||||
password: 'Passwort zur Restic-Verschlüsselung. Beispiel: eine lange zufällige Passphrase. Leer lassen, wenn ein vorhandenes Passwort unverändert bleiben soll.',
|
||||
managed: 'Wenn aktiv, mountet URBM SMB/NFS vor dem Job selbst. Wenn aus, muss der angegebene Pfad bereits durch Unraid oder Unassigned Devices gemountet sein.',
|
||||
remote: 'Adresse der Netzwerkfreigabe. SMB-Beispiel: //192.168.1.20/backup. NFS-Beispiel: 192.168.1.20:/volume1/backup.',
|
||||
@@ -117,15 +117,6 @@
|
||||
const id = (prefix) => `${prefix}-${crypto.randomUUID()}`;
|
||||
const repoName = (repoId) => state.config.repositories.find(r => r.id === repoId)?.name || repoId;
|
||||
const volatileLocation = (repo) => repo.type === 'local' && (/^\/tmp(?:\/|$)/.test(repo.location) || /^\/run(?:\/|$)/.test(repo.location));
|
||||
const repositoryFolderName = (name) => String(name || '')
|
||||
.normalize('NFKD').replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/ß/g, 'ss').replace(/[^a-zA-Z0-9._-]+/g, '-')
|
||||
.replace(/^[._-]+|[._-]+$/g, '').slice(0, 64);
|
||||
const repositoryChildLocation = (base, name) => {
|
||||
const folder = repositoryFolderName(name);
|
||||
if (!folder) throw new Error('Der Repository-Name muss mindestens einen Buchstaben oder eine Zahl enthalten.');
|
||||
return `${String(base || '').replace(/\/+$/,'')}/${folder}`;
|
||||
};
|
||||
const tipAttr = (text) => text ? ` data-tooltip="${esc(text)}"` : '';
|
||||
const actionTooltip = (action, label) => {
|
||||
const [name] = action.split(':');
|
||||
@@ -429,12 +420,10 @@
|
||||
content.innerHTML = `<section class="bu-card"><h2>Repository ${repo.id?'bearbeiten':'erstellen'}</h2><form id="bu-repo-form" class="bu-form"><input type="hidden" name="id" value="${esc(repo.id||'')}"><input type="hidden" name="passwordRef" value="${esc(passwordRef)}"><input type="hidden" name="credentialRef" value="${esc(credentialRef)}"><input type="hidden" name="hasCredential" value="${repo.credentialRef?'true':'false'}">
|
||||
<label>Name<input name="name" required value="${esc(repo.name||'')}"></label><label>Typ<select name="type">${['local','smb','nfs','sftp'].map(v=>`<option value="${v}" ${v===repo.type?'selected':''}>${esc(typeLabels[v])}</option>`).join('')}</select></label>
|
||||
<div id="bu-repository-specific" class="wide"></div>
|
||||
${repo.id?'':'<div class="wide bu-info-card"><h3>Automatischer Repository-Unterordner</h3><p>URBM erstellt im gewählten Basisordner einen Unterordner aus dem Repository-Namen.</p><p>Endgültiger Speicherort: <strong id="bu-repository-final-location">Name und Basisordner eingeben</strong></p></div>'}
|
||||
<fieldset class="wide bu-fieldset"><legend>Restic-Verschlüsselung</legend><label>Repository-Passwort<input name="password" type="password" autocomplete="new-password" ${repo.id?'':'required'} placeholder="${repo.id?'Leer lassen, um das vorhandene Passwort zu behalten':'Ein starkes Repository-Passwort eingeben'}"></label><p class="bu-muted">Das Passwort wird von URBM geschützt gespeichert. ${repo.id?'Leer lassen, um das vorhandene Passwort zu behalten.':'Bewahre dieses Passwort sicher auf; ohne das Passwort können Backups nicht wiederhergestellt werden.'}</p></fieldset>
|
||||
<div class="wide bu-actions">${button('Speichern','submit-repo','primary')}${button('Abbrechen','cancel-form')}</div></form></section>`;
|
||||
enhanceTooltips(content);
|
||||
renderRepositorySpecific(repo.type || 'local', repo);
|
||||
updateRepositoryLocationPreview();
|
||||
}
|
||||
|
||||
function repositoryLocationField(label, value, placeholder) {
|
||||
@@ -445,25 +434,15 @@
|
||||
const host = document.getElementById('bu-repository-specific');
|
||||
if (!host) return;
|
||||
if (type === 'local') {
|
||||
host.innerHTML = `${repositoryLocationField(repo.id?'Lokaler Repository-Ordner':'Lokaler Basisordner',repo.location,'/mnt/user/backups')}<div id="bu-repository-browser"></div>`;
|
||||
host.innerHTML = `${repositoryLocationField('Lokaler Repository-Ordner',repo.location,'/mnt/user/backups/urbm')}<div id="bu-repository-browser"></div>`;
|
||||
renderRepositoryBrowser();
|
||||
} else if (type === 'smb' || type === 'nfs') {
|
||||
const managed = repo.mount?.managed === true;
|
||||
host.innerHTML = `<fieldset class="bu-fieldset"><legend>${type.toUpperCase()}-Ziel</legend><label class="bu-inline-check"><input name="managed" type="checkbox" ${managed?'checked':''}><span>URBM soll diese Freigabe einbinden</span></label><div class="bu-external-mount" ${managed?'hidden':''}>${repositoryLocationField(repo.id?'Bereits eingebundener Repository-Ordner':'Bereits eingebundener Basisordner',repo.location,'/mnt/remotes/backup-nas')}</div><div class="bu-managed-mount" ${managed?'':'hidden'}><div class="bu-form-grid"><label>Netzwerkfreigabe<input name="remote" placeholder="${type==='smb'?'//192.168.1.20/backup':'192.168.1.20:/volume1/backup'}" value="${esc(repo.mount?.remote||'')}"></label><label>${repo.id?'Einhängepunkt':'Basis-Einhängepunkt'}<input name="mountPoint" placeholder="/mnt/remotes/urbm-backup" value="${esc(repo.mount?.mountPoint||repo.location||'')}"></label>${type==='smb'?`<label class="wide">SMB-Zugangsdaten<textarea name="credential" placeholder="${repo.credentialRef?'Leer lassen, um vorhandene Zugangsdaten zu behalten':'username=backup password=geheim'}"></textarea></label>`:''}</div></div></fieldset>`;
|
||||
host.innerHTML = `<fieldset class="bu-fieldset"><legend>${type.toUpperCase()}-Ziel</legend><label class="bu-inline-check"><input name="managed" type="checkbox" ${managed?'checked':''}><span>URBM soll diese Freigabe einbinden</span></label><div class="bu-external-mount" ${managed?'hidden':''}>${repositoryLocationField('Bereits eingebundener Ordner',repo.location,'/mnt/remotes/backup-nas')}</div><div class="bu-managed-mount" ${managed?'':'hidden'}><div class="bu-form-grid"><label>Netzwerkfreigabe<input name="remote" placeholder="${type==='smb'?'//192.168.1.20/backup':'192.168.1.20:/volume1/backup'}" value="${esc(repo.mount?.remote||'')}"></label><label>Einhängepunkt<input name="mountPoint" placeholder="/mnt/remotes/urbm-backup" value="${esc(repo.mount?.mountPoint||repo.location||'')}"></label>${type==='smb'?`<label class="wide">SMB-Zugangsdaten<textarea name="credential" placeholder="${repo.credentialRef?'Leer lassen, um vorhandene Zugangsdaten zu behalten':'username=backup password=geheim'}"></textarea></label>`:''}</div></div></fieldset>`;
|
||||
} else {
|
||||
host.innerHTML = `<fieldset class="bu-fieldset"><legend>SFTP-Ziel</legend><div class="bu-form-grid">${repositoryLocationField(repo.id?'SFTP-Repository-Adresse':'SFTP-Basisadresse',repo.location,'backup@192.168.1.20:/srv/backups')}<label>Pfad zu SFTP known_hosts<input name="knownHostsPath" value="${esc(repo.options?.knownHostsPath||'/root/.ssh/known_hosts')}"></label><label class="wide">Privater SSH-Schlüssel (optional)<textarea name="credential" placeholder="${repo.credentialRef?'Leer lassen, um den vorhandenen Schlüssel zu behalten':'-----BEGIN OPENSSH PRIVATE KEY-----'}"></textarea></label></div></fieldset>`;
|
||||
host.innerHTML = `<fieldset class="bu-fieldset"><legend>SFTP-Ziel</legend><div class="bu-form-grid">${repositoryLocationField('SFTP-Repository-Adresse',repo.location,'backup@192.168.1.20:/srv/backups/urbm')}<label>Pfad zu SFTP known_hosts<input name="knownHostsPath" value="${esc(repo.options?.knownHostsPath||'/root/.ssh/known_hosts')}"></label><label class="wide">Privater SSH-Schlüssel (optional)<textarea name="credential" placeholder="${repo.credentialRef?'Leer lassen, um den vorhandenen Schlüssel zu behalten':'-----BEGIN OPENSSH PRIVATE KEY-----'}"></textarea></label></div></fieldset>`;
|
||||
}
|
||||
enhanceTooltips(host);
|
||||
updateRepositoryLocationPreview();
|
||||
}
|
||||
|
||||
function updateRepositoryLocationPreview() {
|
||||
const form=document.getElementById('bu-repo-form'); const output=document.getElementById('bu-repository-final-location');
|
||||
if(!form||!output||form.elements.id.value) return;
|
||||
const type=form.elements.type.value; const managed=(type==='smb'||type==='nfs')&&form.elements.managed?.checked;
|
||||
const base=managed?form.elements.mountPoint?.value:form.elements.location?.value;
|
||||
try { output.textContent=base&&form.elements.name.value?repositoryChildLocation(base,form.elements.name.value):'Name und Basisordner eingeben'; }
|
||||
catch(error) { output.textContent=error.message; }
|
||||
}
|
||||
|
||||
function renderRepositoryBrowser() {
|
||||
@@ -576,7 +555,7 @@
|
||||
if (name === 'select-current-source') { if(!state.backupSelections.includes(state.sourceBrowserPath)) state.backupSelections.push(state.sourceBrowserPath); return renderSourceBrowser(); }
|
||||
if (name === 'open-repository-dir') { return loadRepositoryDirectories(decodeURIComponent(a)); }
|
||||
if (name === 'repository-up') { const parent=state.repositoryBrowserPath==='/'?'/':state.repositoryBrowserPath.split('/').slice(0,-1).join('/')||'/'; return loadRepositoryDirectories(parent==='/mnt'?'/':parent); }
|
||||
if (name === 'select-repository-folder') { const location=document.querySelector('#bu-repo-form [name="location"]'); if(location) location.value=state.repositoryBrowserPath; updateRepositoryLocationPreview(); return; }
|
||||
if (name === 'select-repository-folder') { const location=document.querySelector('#bu-repo-form [name="location"]'); if(location) location.value=state.repositoryBrowserPath; return; }
|
||||
if (name === 'open-dir') { state.browserPath=decodeURIComponent(a); state.files=await api(`/v1/repositories/${state.snapshotRepo}/snapshots/${state.selectedSnapshot}/files?path=${encodeURIComponent(state.browserPath)}`); return render(); }
|
||||
if (name === 'browser-up') { state.browserPath=state.browserPath.replace(/\/$/,'').split('/').slice(0,-1).join('/')||'/'; state.files=await api(`/v1/repositories/${state.snapshotRepo}/snapshots/${state.selectedSnapshot}/files?path=${encodeURIComponent(state.browserPath)}`); return render(); }
|
||||
if (name === 'select-current-restore') { if(!state.restoreIncludes.includes(state.browserPath)) state.restoreIncludes.push(state.browserPath); return render(); }
|
||||
@@ -605,10 +584,8 @@
|
||||
let location=managed?value('mountPoint'):value('location');
|
||||
if(!location) throw new Error(managed?'Ein Einhängepunkt ist erforderlich.':'Ein Repository-Speicherort ist erforderlich.');
|
||||
if(managed&&!value('remote')) throw new Error('Für eine verwaltete Einbindung ist eine Netzwerkfreigabe erforderlich.');
|
||||
const isNew=!value('id'); const baseLocation=location;
|
||||
if(isNew) location=repositoryChildLocation(baseLocation,value('name'));
|
||||
const usesCredential=type==='sftp'||(type==='smb'&&managed); const credentialRef=usesCredential&&(value('hasCredential')==='true'||Boolean(f.get('credential')))?value('credentialRef'):'';
|
||||
const repo={schemaVersion:1,id:value('id')||id('repo'),name:value('name'),type,location,passwordRef:value('passwordRef'),credentialRef,mount:managed?{managed:true,remote:value('remote'),mountPoint:baseLocation}:null,options:type==='sftp'?{knownHostsPath:value('knownHostsPath')}: {}};
|
||||
const repo={schemaVersion:1,id:value('id')||id('repo'),name:value('name'),type,location,passwordRef:value('passwordRef'),credentialRef,mount:managed?{managed:true,remote:value('remote'),mountPoint:value('mountPoint')}:null,options:type==='sftp'?{knownHostsPath:value('knownHostsPath')}: {}};
|
||||
if(!value('id')&&!f.get('password')) throw new Error('Für ein neues Repository ist ein Passwort erforderlich.');
|
||||
if (volatileLocation(repo) && !confirm('WARNUNG: Dieses Ziel liegt unter /tmp oder /run und geht nach einem Unraid-Neustart verloren. Trotzdem speichern?')) return;
|
||||
if(f.get('password')) await api(`/v1/secrets/${encodeURIComponent(repo.passwordRef)}`,{method:'PUT',body:JSON.stringify({type:'restic-password',value:f.get('password')})});
|
||||
@@ -637,7 +614,6 @@
|
||||
const managed=fieldset?.querySelector('.bu-managed-mount'); const external=fieldset?.querySelector('.bu-external-mount');
|
||||
if(managed) managed.hidden=!e.target.checked;
|
||||
if(external) external.hidden=e.target.checked;
|
||||
updateRepositoryLocationPreview();
|
||||
}
|
||||
if (e.target.name === 'scheduleMode') {
|
||||
const weekdays=e.target.closest('.bu-schedule')?.querySelector('.bu-weekdays');
|
||||
@@ -660,9 +636,6 @@
|
||||
renderWorkloadBrowser(document.querySelector('#bu-job-form [name="type"]')?.value||'docker');
|
||||
}
|
||||
});
|
||||
content.addEventListener('input', e => {
|
||||
if (e.target.closest('#bu-repo-form') && ['name','location','mountPoint'].includes(e.target.name)) updateRepositoryLocationPreview();
|
||||
});
|
||||
content.addEventListener('submit', e => {
|
||||
const form = e.target.closest('form');
|
||||
if (!form) return;
|
||||
|
||||
Reference in New Issue
Block a user