@duplicati/ngclient 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +13 -0
- package/.github/workflows/npm-publish.yml +59 -0
- package/.prettierignore +13 -0
- package/.prettierrc +12 -0
- package/.vscode/extensions.json +4 -0
- package/.vscode/launch.json +13 -0
- package/.vscode/settings.json +8 -0
- package/.vscode/tasks.json +24 -0
- package/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/angular.json +98 -0
- package/bun.lockb +0 -0
- package/openapi-ts.config.ts +13 -0
- package/package.json +46 -0
- package/scripts/generate-package-json.ts +15 -0
- package/src/app/about/about.component.html +15 -0
- package/src/app/about/about.component.scss +19 -0
- package/src/app/about/about.component.ts +13 -0
- package/src/app/about/changelog/changelog.component.html +3 -0
- package/src/app/about/changelog/changelog.component.scss +7 -0
- package/src/app/about/changelog/changelog.component.ts +18 -0
- package/src/app/about/general/general.component.html +9 -0
- package/src/app/about/general/general.component.scss +23 -0
- package/src/app/about/general/general.component.ts +30 -0
- package/src/app/about/libraries/libraries.component.html +3 -0
- package/src/app/about/libraries/libraries.component.scss +7 -0
- package/src/app/about/libraries/libraries.component.ts +18 -0
- package/src/app/about/logs/logs.component.html +5 -0
- package/src/app/about/logs/logs.component.scss +7 -0
- package/src/app/about/logs/logs.component.ts +18 -0
- package/src/app/about/system-info/system-info.component.html +3 -0
- package/src/app/about/system-info/system-info.component.scss +7 -0
- package/src/app/about/system-info/system-info.component.ts +17 -0
- package/src/app/add-backup/add-backup.component.html +29 -0
- package/src/app/add-backup/add-backup.component.scss +38 -0
- package/src/app/add-backup/add-backup.component.ts +14 -0
- package/src/app/app.component.ts +35 -0
- package/src/app/app.config.ts +23 -0
- package/src/app/app.routes.ts +121 -0
- package/src/app/core/components/file-tree/file-tree.component.html +87 -0
- package/src/app/core/components/file-tree/file-tree.component.scss +80 -0
- package/src/app/core/components/file-tree/file-tree.component.ts +485 -0
- package/src/app/core/components/logo/logo.component.html +22 -0
- package/src/app/core/components/logo/logo.component.scss +12 -0
- package/src/app/core/components/logo/logo.component.ts +11 -0
- package/src/app/core/components/status-bar/status-bar.component.html +38 -0
- package/src/app/core/components/status-bar/status-bar.component.scss +27 -0
- package/src/app/core/components/status-bar/status-bar.component.ts +33 -0
- package/src/app/core/components/status-bar/status-bar.state.ts +182 -0
- package/src/app/core/components/toggle-card/toggle-card.component.html +11 -0
- package/src/app/core/components/toggle-card/toggle-card.component.scss +49 -0
- package/src/app/core/components/toggle-card/toggle-card.component.ts +28 -0
- package/src/app/core/interceptors/http.interceptor.ts +69 -0
- package/src/app/core/interceptors/websocket.interceptor.ts +66 -0
- package/src/app/core/openapi/core/ApiError.ts +21 -0
- package/src/app/core/openapi/core/ApiRequestOptions.ts +21 -0
- package/src/app/core/openapi/core/ApiResult.ts +7 -0
- package/src/app/core/openapi/core/OpenAPI.ts +55 -0
- package/src/app/core/openapi/core/request.ts +337 -0
- package/src/app/core/openapi/index.ts +6 -0
- package/src/app/core/openapi/schemas.gen.ts +3611 -0
- package/src/app/core/openapi/services.gen.ts +1460 -0
- package/src/app/core/openapi/types.gen.ts +1510 -0
- package/src/app/core/pipes/duration.pipe.ts +27 -0
- package/src/app/core/pipes/relative-time.pipe.ts +14 -0
- package/src/app/core/providers/dayjs.ts +16 -0
- package/src/app/core/services/dict.en.ts +0 -0
- package/src/app/core/services/dictionary.service.ts +12 -0
- package/src/app/core/services/localstorage.token.ts +82 -0
- package/src/app/core/services/password-generator.service.ts +39 -0
- package/src/app/core/services/relay-websocket.service.ts +268 -0
- package/src/app/core/services/timespan-literals.service.ts +25 -0
- package/src/app/core/states/app-auth.state.ts +87 -0
- package/src/app/core/states/backups.state.ts +71 -0
- package/src/app/core/states/relayconfig.state.ts +23 -0
- package/src/app/core/states/sysinfo.state.ts +31 -0
- package/src/app/core/states/tasks.state.ts +34 -0
- package/src/app/core/types/subscribed.ts +7 -0
- package/src/app/core/validators/custom.validators.ts +120 -0
- package/src/app/create-edit-backup/create-edit-backup.component.html +28 -0
- package/src/app/create-edit-backup/create-edit-backup.component.scss +15 -0
- package/src/app/create-edit-backup/create-edit-backup.component.ts +33 -0
- package/src/app/create-edit-backup/create-edit-backup.state.ts +582 -0
- package/src/app/create-edit-backup/destination/destination.component.html +367 -0
- package/src/app/create-edit-backup/destination/destination.component.scss +85 -0
- package/src/app/create-edit-backup/destination/destination.component.ts +249 -0
- package/src/app/create-edit-backup/destination/destination.config.ts +103 -0
- package/src/app/create-edit-backup/destination/destination.mapper.ts +177 -0
- package/src/app/create-edit-backup/general/general.component.html +108 -0
- package/src/app/create-edit-backup/general/general.component.scss +34 -0
- package/src/app/create-edit-backup/general/general.component.ts +136 -0
- package/src/app/create-edit-backup/options/options.component.html +95 -0
- package/src/app/create-edit-backup/options/options.component.scss +42 -0
- package/src/app/create-edit-backup/options/options.component.ts +113 -0
- package/src/app/create-edit-backup/schedule/schedule.component.html +102 -0
- package/src/app/create-edit-backup/schedule/schedule.component.scss +38 -0
- package/src/app/create-edit-backup/schedule/schedule.component.ts +137 -0
- package/src/app/create-edit-backup/source-data/source-data.component.html +93 -0
- package/src/app/create-edit-backup/source-data/source-data.component.scss +41 -0
- package/src/app/create-edit-backup/source-data/source-data.component.ts +114 -0
- package/src/app/home/backup.const.ts +167 -0
- package/src/app/home/home.component.html +98 -0
- package/src/app/home/home.component.scss +88 -0
- package/src/app/home/home.component.ts +54 -0
- package/src/app/layout/layout.component.html +64 -0
- package/src/app/layout/layout.component.scss +24 -0
- package/src/app/layout/layout.component.ts +51 -0
- package/src/app/layout/layout.state.ts +70 -0
- package/src/app/login/login.component.html +12 -0
- package/src/app/login/login.component.scss +25 -0
- package/src/app/login/login.component.ts +34 -0
- package/src/app/logout/logout.component.html +1 -0
- package/src/app/logout/logout.component.scss +3 -0
- package/src/app/logout/logout.component.ts +18 -0
- package/src/app/restore/restore.component.html +56 -0
- package/src/app/restore/restore.component.scss +37 -0
- package/src/app/restore/restore.component.ts +55 -0
- package/src/app/restore-flow/options/options.component.html +57 -0
- package/src/app/restore-flow/options/options.component.scss +16 -0
- package/src/app/restore-flow/options/options.component.ts +58 -0
- package/src/app/restore-flow/restore-flow.component.html +14 -0
- package/src/app/restore-flow/restore-flow.component.scss +15 -0
- package/src/app/restore-flow/restore-flow.component.ts +23 -0
- package/src/app/restore-flow/restore-flow.state.ts +120 -0
- package/src/app/restore-flow/select-files/select-files.component.html +42 -0
- package/src/app/restore-flow/select-files/select-files.component.scss +16 -0
- package/src/app/restore-flow/select-files/select-files.component.ts +129 -0
- package/src/app/settings/settings.component.html +256 -0
- package/src/app/settings/settings.component.scss +106 -0
- package/src/app/settings/settings.component.ts +475 -0
- package/src/assets/duplicati-logo.png +0 -0
- package/src/assets/duplicati-logo.svg +10 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/images/backup.png +0 -0
- package/src/assets/images/dark-theme.png +0 -0
- package/src/assets/images/folder.png +0 -0
- package/src/assets/images/light-theme.png +0 -0
- package/src/assets/images/new-backup.png +0 -0
- package/src/assets/images/restore.png +0 -0
- package/src/assets/images/system-theme.png +0 -0
- package/src/assets/spk.css +33 -0
- package/src/assets/spk.woff2 +0 -0
- package/src/environments/environment-token.ts +4 -0
- package/src/environments/environment.prod.ts +6 -0
- package/src/environments/environment.ts +6 -0
- package/src/index.html +15 -0
- package/src/main.ts +6 -0
- package/src/proxy.conf.mjs +6 -0
- package/src/styles.scss +41 -0
- package/tsconfig.app.json +15 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { JsonPipe } from '@angular/common';
|
|
2
|
+
import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, signal, viewChild } from '@angular/core';
|
|
3
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
|
5
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
6
|
+
import {
|
|
7
|
+
SparkleButtonComponent,
|
|
8
|
+
SparkleFormFieldComponent,
|
|
9
|
+
SparkleIconComponent,
|
|
10
|
+
SparkleMenuComponent,
|
|
11
|
+
SparkleSelectComponent,
|
|
12
|
+
} from '@sparkle-ui/core';
|
|
13
|
+
import { startWith } from 'rxjs';
|
|
14
|
+
import ToggleCardComponent from '../../core/components/toggle-card/toggle-card.component';
|
|
15
|
+
import { ICommandLineArgument, SettingInputDto } from '../../core/openapi';
|
|
16
|
+
import { SysinfoState } from '../../core/states/sysinfo.state';
|
|
17
|
+
import { CreateEditBackupState } from '../create-edit-backup.state';
|
|
18
|
+
|
|
19
|
+
const fb = new FormBuilder();
|
|
20
|
+
const SIZE_OPTIONS = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'] as const;
|
|
21
|
+
const RETENTION_OPTIONS = ['Local folder', 'Local drive', 'Remote drive', 'Cloud drive'] as const;
|
|
22
|
+
|
|
23
|
+
type SizeOptions = (typeof SIZE_OPTIONS)[number];
|
|
24
|
+
type RetentionOptions = (typeof RETENTION_OPTIONS)[number];
|
|
25
|
+
type AdvancedOption = ReturnType<typeof createAdvancedOption>;
|
|
26
|
+
|
|
27
|
+
export const createOptionsForm = (
|
|
28
|
+
defaults = {
|
|
29
|
+
remoteVolumeSize: 0,
|
|
30
|
+
size: 'GB' as SizeOptions,
|
|
31
|
+
backupRetention: 'Local folder' as RetentionOptions,
|
|
32
|
+
advancedOptions: [],
|
|
33
|
+
}
|
|
34
|
+
) => {
|
|
35
|
+
return fb.group({
|
|
36
|
+
remoteVolumeSize: fb.group({
|
|
37
|
+
size: fb.control<number>(defaults.remoteVolumeSize),
|
|
38
|
+
unit: fb.control<SizeOptions>(defaults.size),
|
|
39
|
+
}),
|
|
40
|
+
backupRetention: fb.control<RetentionOptions>(defaults.backupRetention),
|
|
41
|
+
advancedOptions: fb.array<AdvancedOption>([]),
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const createAdvancedOption = (name: string | null | undefined, defaultValue: string | null | undefined) => {
|
|
46
|
+
return fb.group({
|
|
47
|
+
name: fb.control<SettingInputDto['Name']>(name),
|
|
48
|
+
value: fb.control<SettingInputDto['Value']>(defaultValue),
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
@Component({
|
|
53
|
+
selector: 'app-options',
|
|
54
|
+
standalone: true,
|
|
55
|
+
imports: [
|
|
56
|
+
ReactiveFormsModule,
|
|
57
|
+
SparkleMenuComponent,
|
|
58
|
+
SparkleSelectComponent,
|
|
59
|
+
SparkleButtonComponent,
|
|
60
|
+
SparkleIconComponent,
|
|
61
|
+
SparkleFormFieldComponent,
|
|
62
|
+
ToggleCardComponent,
|
|
63
|
+
JsonPipe,
|
|
64
|
+
],
|
|
65
|
+
templateUrl: './options.component.html',
|
|
66
|
+
styleUrl: './options.component.scss',
|
|
67
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
68
|
+
})
|
|
69
|
+
export default class OptionsComponent {
|
|
70
|
+
#sysinfo = inject(SysinfoState);
|
|
71
|
+
#createEditBackupState = inject(CreateEditBackupState);
|
|
72
|
+
#router = inject(Router);
|
|
73
|
+
#route = inject(ActivatedRoute);
|
|
74
|
+
formRef = viewChild.required<ElementRef<HTMLFormElement>>('formRef');
|
|
75
|
+
|
|
76
|
+
optionsForm = this.#createEditBackupState.optionsForm;
|
|
77
|
+
sizeOptions = signal(SIZE_OPTIONS);
|
|
78
|
+
rentationOptions = signal(RETENTION_OPTIONS);
|
|
79
|
+
selectionAdvancedOptions = toSignal(this.optionsForm.controls.advancedOptions.valueChanges.pipe(startWith([])));
|
|
80
|
+
advancedOptions = signal<ICommandLineArgument[]>(this.#sysinfo.systemInfo()?.Options ?? []);
|
|
81
|
+
|
|
82
|
+
nonSelectedAdvancedOptions = computed(() => {
|
|
83
|
+
return this.advancedOptions().filter(
|
|
84
|
+
(x) => this.selectionAdvancedOptions()?.findIndex((y) => y.name === x.Name) === -1
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
addNewOption(option: ICommandLineArgument) {
|
|
89
|
+
this.optionsForm.controls.advancedOptions.push(createAdvancedOption(option.Name, option.DefaultValue));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
displayFn() {
|
|
93
|
+
const items = this.advancedOptions();
|
|
94
|
+
|
|
95
|
+
return (val: string) => {
|
|
96
|
+
const item = items.find((x) => x.Name === val);
|
|
97
|
+
|
|
98
|
+
if (!item) {
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return `${item.Name}`;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
goBack() {
|
|
107
|
+
this.#router.navigate(['schedule'], { relativeTo: this.#route.parent });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
submit() {
|
|
111
|
+
this.#createEditBackupState.submit();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<form [formGroup]="scheduleForm" #formRef (submit)="next()">
|
|
2
|
+
<h3 class="title-30">Schedule</h3>
|
|
3
|
+
|
|
4
|
+
<div class="toggle-wrap">
|
|
5
|
+
<spk-toggle class="raised primary">
|
|
6
|
+
Automatically run backups
|
|
7
|
+
<input type="checkbox" formControlName="autoRun" />
|
|
8
|
+
</spk-toggle>
|
|
9
|
+
|
|
10
|
+
<p>If a date was missed, the job will run as soon as possible.</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
@if (!scheduleFormSignal()?.autoRun) {
|
|
14
|
+
<div class="row-inputs" formGroupName="nextTime">
|
|
15
|
+
<spk-form-field>
|
|
16
|
+
<label for="time">Next time</label>
|
|
17
|
+
<input type="time" id="time" #timeRef formControlName="time" (focus)="timeRef.showPicker()" />
|
|
18
|
+
<spk-icon spkSuffix>clock</spk-icon>
|
|
19
|
+
</spk-form-field>
|
|
20
|
+
|
|
21
|
+
<spk-form-field>
|
|
22
|
+
<label for="date"></label>
|
|
23
|
+
<input type="date" id="date" #dateRef formControlName="date" (focus)="dateRef.showPicker()" />
|
|
24
|
+
<spk-icon spkSuffix>calendar</spk-icon>
|
|
25
|
+
</spk-form-field>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<ng-container formGroupName="runAgain">
|
|
29
|
+
<div class="row-inputs">
|
|
30
|
+
<spk-form-field>
|
|
31
|
+
<label for="repeatValue">Run again every</label>
|
|
32
|
+
<input type="number" id="repeatValue" formControlName="repeatValue" />
|
|
33
|
+
</spk-form-field>
|
|
34
|
+
|
|
35
|
+
<spk-select [displayFn]="displayFn">
|
|
36
|
+
<input type="text" id="repeatUnit" formControlName="repeatUnit" />
|
|
37
|
+
|
|
38
|
+
<ng-container options>
|
|
39
|
+
@for (unit of unitOptions; track $index) {
|
|
40
|
+
<option [value]="unit.key">{{ unit.label }}</option>
|
|
41
|
+
}
|
|
42
|
+
</ng-container>
|
|
43
|
+
</spk-select>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<app-toggle-card formGroupName="allowedDays" [disallowToggle]="true">
|
|
47
|
+
<ng-container title>Allowed days</ng-container>
|
|
48
|
+
|
|
49
|
+
<div class="days-wrap">
|
|
50
|
+
<spk-toggle class="raised primary">
|
|
51
|
+
Monday
|
|
52
|
+
<input type="checkbox" formControlName="mon" />
|
|
53
|
+
</spk-toggle>
|
|
54
|
+
|
|
55
|
+
<spk-toggle class="raised primary">
|
|
56
|
+
Tuesday
|
|
57
|
+
<input type="checkbox" formControlName="tue" />
|
|
58
|
+
</spk-toggle>
|
|
59
|
+
|
|
60
|
+
<spk-toggle class="raised primary">
|
|
61
|
+
Wednesday
|
|
62
|
+
<input type="checkbox" formControlName="wed" />
|
|
63
|
+
</spk-toggle>
|
|
64
|
+
|
|
65
|
+
<spk-toggle class="raised primary">
|
|
66
|
+
Thursday
|
|
67
|
+
<input type="checkbox" formControlName="thu" />
|
|
68
|
+
</spk-toggle>
|
|
69
|
+
|
|
70
|
+
<spk-toggle class="raised primary">
|
|
71
|
+
Friday
|
|
72
|
+
<input type="checkbox" formControlName="fri" />
|
|
73
|
+
</spk-toggle>
|
|
74
|
+
|
|
75
|
+
<spk-toggle class="raised primary">
|
|
76
|
+
Saturday
|
|
77
|
+
<input type="checkbox" formControlName="sat" />
|
|
78
|
+
</spk-toggle>
|
|
79
|
+
|
|
80
|
+
<spk-toggle class="raised primary">
|
|
81
|
+
Sunday
|
|
82
|
+
<input type="checkbox" formControlName="sun" />
|
|
83
|
+
</spk-toggle>
|
|
84
|
+
</div>
|
|
85
|
+
</app-toggle-card>
|
|
86
|
+
</ng-container>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
<div class="form-actions">
|
|
90
|
+
<button spk-button type="button" (click)="goBack()">
|
|
91
|
+
<spk-icon>arrow-left</spk-icon>
|
|
92
|
+
Go back
|
|
93
|
+
</button>
|
|
94
|
+
|
|
95
|
+
<button spk-button class="raised primary" type="submit">
|
|
96
|
+
<spk-icon>arrow-right</spk-icon>
|
|
97
|
+
Continue
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
</form>
|
|
101
|
+
|
|
102
|
+
<!-- <pre>{{ scheduleForm.value | json }}</pre> -->
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
@use '@sparkle-ui/core/styles/helpers' as *;
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
form {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: p2r(24);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.toggle-wrap {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: p2r(4);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.row-inputs {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: flex-end;
|
|
22
|
+
gap: p2r(8);
|
|
23
|
+
|
|
24
|
+
> * {
|
|
25
|
+
flex: 1 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.days-wrap {
|
|
30
|
+
display: grid;
|
|
31
|
+
grid-template-columns: 1fr 1fr;
|
|
32
|
+
gap: p2r(16);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.form-actions {
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { JsonPipe } from '@angular/common';
|
|
2
|
+
import { ChangeDetectionStrategy, Component, ElementRef, inject, viewChild } from '@angular/core';
|
|
3
|
+
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
|
4
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
5
|
+
import {
|
|
6
|
+
SparkleButtonComponent,
|
|
7
|
+
SparkleFormFieldComponent,
|
|
8
|
+
SparkleIconComponent,
|
|
9
|
+
SparkleSelectComponent,
|
|
10
|
+
SparkleToggleComponent,
|
|
11
|
+
} from '@sparkle-ui/core';
|
|
12
|
+
import ToggleCardComponent from '../../core/components/toggle-card/toggle-card.component';
|
|
13
|
+
import { CreateEditBackupState } from '../create-edit-backup.state';
|
|
14
|
+
|
|
15
|
+
const fb = new FormBuilder();
|
|
16
|
+
|
|
17
|
+
const UNIT_OPTIONS = [
|
|
18
|
+
{
|
|
19
|
+
key: 's',
|
|
20
|
+
label: 'Seconds',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: 'm',
|
|
24
|
+
label: 'Minutes',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'h',
|
|
28
|
+
label: 'Hours',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: 'D',
|
|
32
|
+
label: 'Days',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: 'W',
|
|
36
|
+
label: 'Weeks',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: 'M',
|
|
40
|
+
label: 'Months',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: 'Y',
|
|
44
|
+
label: 'Years',
|
|
45
|
+
},
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
type Unit = (typeof UNIT_OPTIONS)[number]['key'];
|
|
49
|
+
export type Days = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
50
|
+
|
|
51
|
+
export const createScheduleForm = (
|
|
52
|
+
defaults = {
|
|
53
|
+
autoRun: false,
|
|
54
|
+
nextTime: { time: '13:00', date: new Date().toISOString().split('T')[0] },
|
|
55
|
+
runAgain: {
|
|
56
|
+
repeatValue: 1,
|
|
57
|
+
repeatUnit: 'D' as Unit,
|
|
58
|
+
allowedDays: {
|
|
59
|
+
mon: false,
|
|
60
|
+
tue: false,
|
|
61
|
+
wed: false,
|
|
62
|
+
thu: false,
|
|
63
|
+
fri: false,
|
|
64
|
+
sat: false,
|
|
65
|
+
sun: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
) => {
|
|
70
|
+
return fb.group({
|
|
71
|
+
autoRun: fb.control<boolean>(defaults.autoRun),
|
|
72
|
+
nextTime: fb.group({
|
|
73
|
+
time: fb.control<string>(defaults.nextTime.time),
|
|
74
|
+
date: fb.control<string>(defaults.nextTime.date),
|
|
75
|
+
}),
|
|
76
|
+
runAgain: fb.group({
|
|
77
|
+
repeatValue: fb.control<number>(defaults.runAgain.repeatValue),
|
|
78
|
+
repeatUnit: fb.control<Unit>(defaults.runAgain.repeatUnit),
|
|
79
|
+
allowedDays: fb.group({
|
|
80
|
+
mon: fb.control<boolean>(defaults.runAgain.allowedDays.mon),
|
|
81
|
+
tue: fb.control<boolean>(defaults.runAgain.allowedDays.tue),
|
|
82
|
+
wed: fb.control<boolean>(defaults.runAgain.allowedDays.wed),
|
|
83
|
+
thu: fb.control<boolean>(defaults.runAgain.allowedDays.thu),
|
|
84
|
+
fri: fb.control<boolean>(defaults.runAgain.allowedDays.fri),
|
|
85
|
+
sat: fb.control<boolean>(defaults.runAgain.allowedDays.sat),
|
|
86
|
+
sun: fb.control<boolean>(defaults.runAgain.allowedDays.sun),
|
|
87
|
+
}),
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type ScheduleFormValue = ReturnType<typeof createScheduleForm>['value'];
|
|
93
|
+
|
|
94
|
+
@Component({
|
|
95
|
+
selector: 'app-schedule',
|
|
96
|
+
standalone: true,
|
|
97
|
+
imports: [
|
|
98
|
+
ReactiveFormsModule,
|
|
99
|
+
SparkleFormFieldComponent,
|
|
100
|
+
SparkleSelectComponent,
|
|
101
|
+
SparkleToggleComponent,
|
|
102
|
+
SparkleIconComponent,
|
|
103
|
+
SparkleButtonComponent,
|
|
104
|
+
ToggleCardComponent,
|
|
105
|
+
JsonPipe,
|
|
106
|
+
],
|
|
107
|
+
templateUrl: './schedule.component.html',
|
|
108
|
+
styleUrl: './schedule.component.scss',
|
|
109
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
110
|
+
})
|
|
111
|
+
export default class ScheduleComponent {
|
|
112
|
+
#createEditBackupState = inject(CreateEditBackupState);
|
|
113
|
+
#router = inject(Router);
|
|
114
|
+
#route = inject(ActivatedRoute);
|
|
115
|
+
formRef = viewChild.required<ElementRef<HTMLFormElement>>('formRef');
|
|
116
|
+
|
|
117
|
+
scheduleForm = this.#createEditBackupState.scheduleForm;
|
|
118
|
+
scheduleFormSignal = this.#createEditBackupState.scheduleFormSignal;
|
|
119
|
+
unitOptions = UNIT_OPTIONS;
|
|
120
|
+
|
|
121
|
+
displayFn(val: Unit) {
|
|
122
|
+
const item = UNIT_OPTIONS.find((x) => x.key === val);
|
|
123
|
+
|
|
124
|
+
if (!item) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
return `${item.label}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
goBack() {
|
|
131
|
+
this.#router.navigate(['source-data'], { relativeTo: this.#route.parent });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
next() {
|
|
135
|
+
this.#router.navigate(['options'], { relativeTo: this.#route.parent });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
@if (destinationIsLoaded()) {
|
|
2
|
+
<form [formGroup]="sourceDataForm" #formRef (submit)="next()">
|
|
3
|
+
<h3 class="title-30">Source Data</h3>
|
|
4
|
+
|
|
5
|
+
<app-file-tree [multiSelect]="true" [startingPath]="getPath()">
|
|
6
|
+
<label for="path">Folder path</label>
|
|
7
|
+
<input type="text" id="path" tabindex="4" placeholder="Path" formControlName="path" />
|
|
8
|
+
</app-file-tree>
|
|
9
|
+
|
|
10
|
+
<!-- <app-toggle-card formArrayName="filters" [isActive]="true">
|
|
11
|
+
<ng-container title>Filters</ng-container>
|
|
12
|
+
|
|
13
|
+
@for (filterGroup of sourceDataForm.controls.filters.controls; track $index) {
|
|
14
|
+
<div class="filter-inputs" [formGroup]="filterGroup">
|
|
15
|
+
<spk-select>
|
|
16
|
+
<input type="text" placeholder="Select an option..." formControlName="expressionOption" />
|
|
17
|
+
|
|
18
|
+
<ng-container options>
|
|
19
|
+
@for (option of expressionOptions(); track $index) {
|
|
20
|
+
<option [value]="option">{{ option }}</option>
|
|
21
|
+
}
|
|
22
|
+
</ng-container>
|
|
23
|
+
</spk-select>
|
|
24
|
+
|
|
25
|
+
<spk-form-field>
|
|
26
|
+
<input type="text" formControlName="expression" />
|
|
27
|
+
</spk-form-field>
|
|
28
|
+
|
|
29
|
+
<div class="filter-removal" (click)="removeFilterGroup($index)">
|
|
30
|
+
<spk-icon class="spk-primary">x-circle</spk-icon>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
<button type="button" spk-button (click)="addFilterGroup()">Add filter</button>
|
|
36
|
+
</app-toggle-card> -->
|
|
37
|
+
|
|
38
|
+
<app-toggle-card formGroupName="excludes" [isActive]="true">
|
|
39
|
+
<ng-container title>Exclude</ng-container>
|
|
40
|
+
|
|
41
|
+
<spk-toggle class="raised primary">
|
|
42
|
+
Hidden files
|
|
43
|
+
<input type="checkbox" placeholder="Hidden files" formControlName="hidden" />
|
|
44
|
+
</spk-toggle>
|
|
45
|
+
|
|
46
|
+
<spk-toggle class="raised primary">
|
|
47
|
+
System files
|
|
48
|
+
<input type="checkbox" placeholder="System files" formControlName="system" />
|
|
49
|
+
</spk-toggle>
|
|
50
|
+
|
|
51
|
+
<spk-toggle class="raised primary">
|
|
52
|
+
Temporary files
|
|
53
|
+
<input type="checkbox" placeholder="Temporary files" formControlName="temporary" />
|
|
54
|
+
</spk-toggle>
|
|
55
|
+
|
|
56
|
+
<spk-toggle class="raised primary" [class.active]="filesLargerThan()" (click)="toggleFilesLargerThan()">
|
|
57
|
+
Files larger than
|
|
58
|
+
</spk-toggle>
|
|
59
|
+
|
|
60
|
+
@if (filesLargerThan()) {
|
|
61
|
+
<div class="row-inputs" formGroupName="filesLargerThan">
|
|
62
|
+
<spk-form-field>
|
|
63
|
+
<input type="number" formControlName="size" />
|
|
64
|
+
</spk-form-field>
|
|
65
|
+
|
|
66
|
+
<spk-select>
|
|
67
|
+
<input type="text" formControlName="unit" placeholder="Select an option..." />
|
|
68
|
+
|
|
69
|
+
<ng-container options>
|
|
70
|
+
@for (option of sizeOptions(); track $index) {
|
|
71
|
+
<option [value]="option">{{ option }}</option>
|
|
72
|
+
}
|
|
73
|
+
</ng-container>
|
|
74
|
+
</spk-select>
|
|
75
|
+
</div>
|
|
76
|
+
}
|
|
77
|
+
</app-toggle-card>
|
|
78
|
+
|
|
79
|
+
<div class="form-actions">
|
|
80
|
+
<button spk-button type="button" (click)="goBack()">
|
|
81
|
+
<spk-icon>arrow-left</spk-icon>
|
|
82
|
+
Go back
|
|
83
|
+
</button>
|
|
84
|
+
|
|
85
|
+
<button spk-button class="raised primary" type="submit">
|
|
86
|
+
<spk-icon>arrow-right</spk-icon>
|
|
87
|
+
Continue
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</form>
|
|
91
|
+
|
|
92
|
+
<!-- <pre>{{ sourceDataForm.value | json }}</pre> -->
|
|
93
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
@use '@sparkle-ui/core/styles/helpers' as *;
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
form {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: p2r(24);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.form-actions {
|
|
14
|
+
display: flex;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.row-inputs {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: 1fr min-content;
|
|
21
|
+
gap: p2r(8);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.filter-inputs {
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: p2r(8);
|
|
27
|
+
align-items: flex-end;
|
|
28
|
+
|
|
29
|
+
> * {
|
|
30
|
+
flex: 1 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.filter-removal {
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
height: p2r(40);
|
|
39
|
+
flex: 0 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { JsonPipe } from '@angular/common';
|
|
2
|
+
import { ChangeDetectionStrategy, Component, computed, ElementRef, inject, signal, viewChild } from '@angular/core';
|
|
3
|
+
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
|
4
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
5
|
+
import {
|
|
6
|
+
SparkleButtonComponent,
|
|
7
|
+
SparkleFormFieldComponent,
|
|
8
|
+
SparkleIconComponent,
|
|
9
|
+
SparkleSelectComponent,
|
|
10
|
+
SparkleToggleComponent,
|
|
11
|
+
} from '@sparkle-ui/core';
|
|
12
|
+
import FileTreeComponent from '../../core/components/file-tree/file-tree.component';
|
|
13
|
+
import ToggleCardComponent from '../../core/components/toggle-card/toggle-card.component';
|
|
14
|
+
import { CreateEditBackupState } from '../create-edit-backup.state';
|
|
15
|
+
|
|
16
|
+
const fb = new FormBuilder();
|
|
17
|
+
const SIZE_OPTIONS = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'] as const;
|
|
18
|
+
const EXPRESSION_OPTIONS = ['Contains', 'Does not contain', 'Is greater than', 'Is less than'] as const;
|
|
19
|
+
|
|
20
|
+
type SizeOptions = (typeof SIZE_OPTIONS)[number] | null;
|
|
21
|
+
type ExpressionOptions = (typeof EXPRESSION_OPTIONS)[number];
|
|
22
|
+
|
|
23
|
+
const createExpressionGroup = () => {
|
|
24
|
+
return fb.group({
|
|
25
|
+
expressionOption: fb.control<ExpressionOptions>(EXPRESSION_OPTIONS[0]),
|
|
26
|
+
expression: fb.nonNullable.control<string>('*'),
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ExpressionGroup = ReturnType<typeof createExpressionGroup>;
|
|
31
|
+
|
|
32
|
+
export const createSourceDataForm = (
|
|
33
|
+
defaults = {
|
|
34
|
+
path: '',
|
|
35
|
+
excludes: {
|
|
36
|
+
hidden: false,
|
|
37
|
+
system: false,
|
|
38
|
+
temporary: false,
|
|
39
|
+
filesLargerThan: null,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
) => {
|
|
43
|
+
return fb.group({
|
|
44
|
+
path: fb.control<string>(defaults.path),
|
|
45
|
+
excludes: fb.group({
|
|
46
|
+
hidden: fb.control<boolean>(defaults.excludes.hidden),
|
|
47
|
+
system: fb.control<boolean>(defaults.excludes.system),
|
|
48
|
+
temporary: fb.control<boolean>(defaults.excludes.temporary),
|
|
49
|
+
filesLargerThan: fb.group({
|
|
50
|
+
size: fb.control<number | null>(null),
|
|
51
|
+
unit: fb.control<string | null>(null),
|
|
52
|
+
}),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
@Component({
|
|
58
|
+
selector: 'app-source-data',
|
|
59
|
+
standalone: true,
|
|
60
|
+
imports: [
|
|
61
|
+
ReactiveFormsModule,
|
|
62
|
+
SparkleFormFieldComponent,
|
|
63
|
+
SparkleIconComponent,
|
|
64
|
+
SparkleButtonComponent,
|
|
65
|
+
SparkleSelectComponent,
|
|
66
|
+
SparkleToggleComponent,
|
|
67
|
+
FileTreeComponent,
|
|
68
|
+
ToggleCardComponent,
|
|
69
|
+
|
|
70
|
+
JsonPipe,
|
|
71
|
+
],
|
|
72
|
+
templateUrl: './source-data.component.html',
|
|
73
|
+
styleUrl: './source-data.component.scss',
|
|
74
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
75
|
+
})
|
|
76
|
+
export default class SourceDataComponent {
|
|
77
|
+
#createEditBackupState = inject(CreateEditBackupState);
|
|
78
|
+
#router = inject(Router);
|
|
79
|
+
#route = inject(ActivatedRoute);
|
|
80
|
+
formRef = viewChild.required<ElementRef<HTMLFormElement>>('formRef');
|
|
81
|
+
sourceDataForm = this.#createEditBackupState.sourceDataForm;
|
|
82
|
+
sourceDataFormSignal = this.#createEditBackupState.sourceDataFormSignal;
|
|
83
|
+
destinationIsLoaded = this.#createEditBackupState.destinationIsLoaded;
|
|
84
|
+
|
|
85
|
+
filesLargerThan = computed(() => this.sourceDataFormSignal()?.excludes?.filesLargerThan?.size !== null);
|
|
86
|
+
sizeOptions = signal(SIZE_OPTIONS);
|
|
87
|
+
expressionOptions = signal(EXPRESSION_OPTIONS);
|
|
88
|
+
|
|
89
|
+
getPath() {
|
|
90
|
+
return this.sourceDataForm.value.path ?? null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
toggleFilesLargerThan() {
|
|
94
|
+
if (this.sourceDataForm.controls.excludes.value.filesLargerThan?.size === null) {
|
|
95
|
+
this.sourceDataForm.controls.excludes.controls.filesLargerThan.setValue({
|
|
96
|
+
size: 50,
|
|
97
|
+
unit: 'MB',
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
this.sourceDataForm.controls.excludes.controls.filesLargerThan.setValue({
|
|
101
|
+
size: null,
|
|
102
|
+
unit: null,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
goBack() {
|
|
108
|
+
this.#router.navigate(['destination'], { relativeTo: this.#route.parent });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
next() {
|
|
112
|
+
this.#router.navigate(['schedule'], { relativeTo: this.#route.parent });
|
|
113
|
+
}
|
|
114
|
+
}
|