diff --git a/src/app/interfaces/cloud-backup.interface.ts b/src/app/interfaces/cloud-backup.interface.ts index 9f783839993..50cbeb05305 100644 --- a/src/app/interfaces/cloud-backup.interface.ts +++ b/src/app/interfaces/cloud-backup.interface.ts @@ -1,3 +1,4 @@ +import { marker as T } from '@biesbjerg/ngx-translate-extract-marker'; import { ApiTimestamp } from 'app/interfaces/api-date.interface'; import { Job } from 'app/interfaces/job.interface'; import { BwLimit, BwLimitUpdate, CloudCredential } from './cloud-sync-task.interface'; @@ -51,6 +52,13 @@ export enum SnapshotIncludeExclude { ExcludeByPattern = 'excludeByPattern', } +export const snapshotIncludeExcludeOptions = new Map([ + [SnapshotIncludeExclude.IncludeEverything, T('Include everything')], + [SnapshotIncludeExclude.IncludeFromSubFolder, T('Include from subfolder')], + [SnapshotIncludeExclude.ExcludePaths, T('Select paths to exclude')], + [SnapshotIncludeExclude.ExcludeByPattern, T('Exclude by pattern')], +]); + export type CloudBackupRestoreParams = [ id: number, snapshot_id: string, diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.html b/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.html index 8a257f61f82..8829f46b880 100644 --- a/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.html +++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.html @@ -48,7 +48,7 @@ @if (isIncludeFromSubfolderSelected) { { provide: SLIDE_IN_DATA, useValue: { snapshot: { id: 1 }, - backup: { id: 1, path: '/mnt/backup/path' }, + backup: { id: 1, path: '/mnt/dozer' }, }, }, ], @@ -56,7 +56,7 @@ describe('CloudBackupRestoreFromSnapshotFormComponent', () => { it('submits backup restore from snapshot with `Include Everything`', async () => { spectator.component.form.patchValue({ - target: '/mnt/my pool', + target: '/mnt/bulldozer', }); const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' })); @@ -65,20 +65,18 @@ describe('CloudBackupRestoreFromSnapshotFormComponent', () => { expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith('cloud_backup.restore', [ 1, 1, - '/mnt/backup/path', - '/mnt/my pool', + '/mnt/dozer', + '/mnt/bulldozer', {}, ]); }); it('submits backup restore from snapshot with `Select paths to exclude`', async () => { - spectator.component.form.patchValue({ - target: '/mnt/my pool', - includeExclude: SnapshotIncludeExclude.ExcludePaths, - }); - - spectator.component.form.patchValue({ - excludedPaths: ['/mnt/another'], + const form = await loader.getHarness(IxFormHarness); + await form.fillForm({ + Target: '/mnt/bulldozer', + 'Include/Exclude': 'Select paths to exclude', + 'Excluded Paths': '/mnt/dozer/another', }); const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' })); @@ -87,25 +85,48 @@ describe('CloudBackupRestoreFromSnapshotFormComponent', () => { expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith('cloud_backup.restore', [ 1, 1, - '/mnt/backup/path', - '/mnt/my pool', + '/mnt/dozer', + '/mnt/bulldozer', { exclude: [ - '/mnt/another', + '/another', ], }, ]); }); it('submits backup restore from snapshot with `Include from subfolder`', async () => { - spectator.component.form.patchValue({ - target: '/mnt/my pool', - includeExclude: SnapshotIncludeExclude.IncludeFromSubFolder, + const form = await loader.getHarness(IxFormHarness); + await form.fillForm({ + Target: '/mnt/bulldozer', + 'Include/Exclude': 'Include from subfolder', + Subfolder: '/mnt/dozer', + 'Included Paths': '/mnt/dozer/a', }); - spectator.component.form.patchValue({ - subFolder: '/test', - includedPaths: ['/test/first'], + const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' })); + await saveButton.click(); + + expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith('cloud_backup.restore', [ + 1, + 1, + '/mnt/dozer', + '/mnt/bulldozer', + { + include: [ + '/a', + ], + }, + ]); + }); + + it('submits backup restore from snapshot with `Include from subfolder` matches paths', async () => { + const form = await loader.getHarness(IxFormHarness); + await form.fillForm({ + Target: '/mnt/bulldozer', + 'Include/Exclude': 'Include from subfolder', + Subfolder: '/mnt/dozer/a', + 'Included Paths': '/mnt/dozer/a', }); const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' })); @@ -114,24 +135,22 @@ describe('CloudBackupRestoreFromSnapshotFormComponent', () => { expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith('cloud_backup.restore', [ 1, 1, - '/test', - '/mnt/my pool', + '/mnt/dozer/a', + '/mnt/bulldozer', { include: [ - '/test/first', + '/', ], }, ]); }); it('submits backup restore from snapshot with `Exclude by pattern`', async () => { - spectator.component.form.patchValue({ - target: '/mnt/my pool', - includeExclude: SnapshotIncludeExclude.ExcludeByPattern, - }); - - spectator.component.form.patchValue({ - excludePattern: 'pattern', + const form = await loader.getHarness(IxFormHarness); + await form.fillForm({ + Target: '/mnt/bulldozer', + 'Include/Exclude': 'Exclude by pattern', + Pattern: 'pattern', }); const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' })); @@ -140,8 +159,8 @@ describe('CloudBackupRestoreFromSnapshotFormComponent', () => { expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith('cloud_backup.restore', [ 1, 1, - '/mnt/backup/path', - '/mnt/my pool', + '/mnt/dozer', + '/mnt/bulldozer', { exclude: [ 'pattern', diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.ts b/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.ts index bf429daf434..ff74661188b 100644 --- a/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.ts +++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-restore-form-snapshot-form/cloud-backup-restore-from-snapshot-form.component.ts @@ -19,6 +19,7 @@ import { CloudBackupSnapshot, CloudBackupSnapshotDirectoryFileType, SnapshotIncludeExclude, + snapshotIncludeExcludeOptions, } from 'app/interfaces/cloud-backup.interface'; import { DatasetCreate } from 'app/interfaces/dataset.interface'; import { ExplorerNodeData, TreeNode } from 'app/interfaces/tree-node.interface'; @@ -44,13 +45,7 @@ export class CloudBackupRestoreFromSnapshotFormComponent implements OnInit { fileNodeProvider: TreeNodeProvider; snapshotNodeProvider: TreeNodeProvider; - readonly includeExcludeOptions = new Map([ - [SnapshotIncludeExclude.IncludeEverything, this.translate.instant('Include everything')], - [SnapshotIncludeExclude.IncludeFromSubFolder, this.translate.instant('Include from subfolder')], - [SnapshotIncludeExclude.ExcludePaths, this.translate.instant('Select paths to exclude')], - [SnapshotIncludeExclude.ExcludeByPattern, this.translate.instant('Exclude by pattern')], - ]); - readonly includeExcludeOptions$ = of(mapToOptions(this.includeExcludeOptions, this.translate)); + readonly includeExcludeOptions$ = of(mapToOptions(snapshotIncludeExcludeOptions, this.translate)); get title(): string { return this.translate.instant('Restore from Snapshot'); @@ -61,7 +56,7 @@ export class CloudBackupRestoreFromSnapshotFormComponent implements OnInit { includeExclude: [SnapshotIncludeExclude.IncludeEverything, Validators.required], excludedPaths: [[] as string[], Validators.required], excludePattern: [null as string | null, Validators.required], - subFolder: [mntPath], + subFolder: [this.data.backup.path], includedPaths: [[] as string[]], }); @@ -105,11 +100,15 @@ export class CloudBackupRestoreFromSnapshotFormComponent implements OnInit { onSubmit(): void { this.isLoading = true; + const subfolder = this.isIncludeFromSubfolderSelected ? this.form.controls.subFolder.value : this.data.backup.path; + const options = { exclude: this.isExcludeByPatternSelected ? [this.form.controls.excludePattern.value] - : this.form.controls.excludedPaths.value, - include: this.isIncludeFromSubfolderSelected ? this.form.value.includedPaths : null, + : this.form.controls.excludedPaths.value.map((path) => path.replace(subfolder, '') || '/'), + include: this.isIncludeFromSubfolderSelected + ? this.form.value.includedPaths.map((path) => path.replace(subfolder, '') || '/') + : null, }; if (!options.exclude?.length) delete options.exclude; @@ -118,11 +117,10 @@ export class CloudBackupRestoreFromSnapshotFormComponent implements OnInit { const params: CloudBackupRestoreParams = [ this.data.backup.id, this.data.snapshot.id, - this.isIncludeFromSubfolderSelected ? this.form.controls.subFolder.value : this.data.backup.path, + subfolder, this.form.controls.target.value, options, ]; - this.ws.job('cloud_backup.restore', params) .pipe(untilDestroyed(this)) .subscribe({