@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.
Files changed (152) hide show
  1. package/.dockerignore +13 -0
  2. package/.github/workflows/npm-publish.yml +59 -0
  3. package/.prettierignore +13 -0
  4. package/.prettierrc +12 -0
  5. package/.vscode/extensions.json +4 -0
  6. package/.vscode/launch.json +13 -0
  7. package/.vscode/settings.json +8 -0
  8. package/.vscode/tasks.json +24 -0
  9. package/Dockerfile +38 -0
  10. package/LICENSE +21 -0
  11. package/README.md +27 -0
  12. package/angular.json +98 -0
  13. package/bun.lockb +0 -0
  14. package/openapi-ts.config.ts +13 -0
  15. package/package.json +46 -0
  16. package/scripts/generate-package-json.ts +15 -0
  17. package/src/app/about/about.component.html +15 -0
  18. package/src/app/about/about.component.scss +19 -0
  19. package/src/app/about/about.component.ts +13 -0
  20. package/src/app/about/changelog/changelog.component.html +3 -0
  21. package/src/app/about/changelog/changelog.component.scss +7 -0
  22. package/src/app/about/changelog/changelog.component.ts +18 -0
  23. package/src/app/about/general/general.component.html +9 -0
  24. package/src/app/about/general/general.component.scss +23 -0
  25. package/src/app/about/general/general.component.ts +30 -0
  26. package/src/app/about/libraries/libraries.component.html +3 -0
  27. package/src/app/about/libraries/libraries.component.scss +7 -0
  28. package/src/app/about/libraries/libraries.component.ts +18 -0
  29. package/src/app/about/logs/logs.component.html +5 -0
  30. package/src/app/about/logs/logs.component.scss +7 -0
  31. package/src/app/about/logs/logs.component.ts +18 -0
  32. package/src/app/about/system-info/system-info.component.html +3 -0
  33. package/src/app/about/system-info/system-info.component.scss +7 -0
  34. package/src/app/about/system-info/system-info.component.ts +17 -0
  35. package/src/app/add-backup/add-backup.component.html +29 -0
  36. package/src/app/add-backup/add-backup.component.scss +38 -0
  37. package/src/app/add-backup/add-backup.component.ts +14 -0
  38. package/src/app/app.component.ts +35 -0
  39. package/src/app/app.config.ts +23 -0
  40. package/src/app/app.routes.ts +121 -0
  41. package/src/app/core/components/file-tree/file-tree.component.html +87 -0
  42. package/src/app/core/components/file-tree/file-tree.component.scss +80 -0
  43. package/src/app/core/components/file-tree/file-tree.component.ts +485 -0
  44. package/src/app/core/components/logo/logo.component.html +22 -0
  45. package/src/app/core/components/logo/logo.component.scss +12 -0
  46. package/src/app/core/components/logo/logo.component.ts +11 -0
  47. package/src/app/core/components/status-bar/status-bar.component.html +38 -0
  48. package/src/app/core/components/status-bar/status-bar.component.scss +27 -0
  49. package/src/app/core/components/status-bar/status-bar.component.ts +33 -0
  50. package/src/app/core/components/status-bar/status-bar.state.ts +182 -0
  51. package/src/app/core/components/toggle-card/toggle-card.component.html +11 -0
  52. package/src/app/core/components/toggle-card/toggle-card.component.scss +49 -0
  53. package/src/app/core/components/toggle-card/toggle-card.component.ts +28 -0
  54. package/src/app/core/interceptors/http.interceptor.ts +69 -0
  55. package/src/app/core/interceptors/websocket.interceptor.ts +66 -0
  56. package/src/app/core/openapi/core/ApiError.ts +21 -0
  57. package/src/app/core/openapi/core/ApiRequestOptions.ts +21 -0
  58. package/src/app/core/openapi/core/ApiResult.ts +7 -0
  59. package/src/app/core/openapi/core/OpenAPI.ts +55 -0
  60. package/src/app/core/openapi/core/request.ts +337 -0
  61. package/src/app/core/openapi/index.ts +6 -0
  62. package/src/app/core/openapi/schemas.gen.ts +3611 -0
  63. package/src/app/core/openapi/services.gen.ts +1460 -0
  64. package/src/app/core/openapi/types.gen.ts +1510 -0
  65. package/src/app/core/pipes/duration.pipe.ts +27 -0
  66. package/src/app/core/pipes/relative-time.pipe.ts +14 -0
  67. package/src/app/core/providers/dayjs.ts +16 -0
  68. package/src/app/core/services/dict.en.ts +0 -0
  69. package/src/app/core/services/dictionary.service.ts +12 -0
  70. package/src/app/core/services/localstorage.token.ts +82 -0
  71. package/src/app/core/services/password-generator.service.ts +39 -0
  72. package/src/app/core/services/relay-websocket.service.ts +268 -0
  73. package/src/app/core/services/timespan-literals.service.ts +25 -0
  74. package/src/app/core/states/app-auth.state.ts +87 -0
  75. package/src/app/core/states/backups.state.ts +71 -0
  76. package/src/app/core/states/relayconfig.state.ts +23 -0
  77. package/src/app/core/states/sysinfo.state.ts +31 -0
  78. package/src/app/core/states/tasks.state.ts +34 -0
  79. package/src/app/core/types/subscribed.ts +7 -0
  80. package/src/app/core/validators/custom.validators.ts +120 -0
  81. package/src/app/create-edit-backup/create-edit-backup.component.html +28 -0
  82. package/src/app/create-edit-backup/create-edit-backup.component.scss +15 -0
  83. package/src/app/create-edit-backup/create-edit-backup.component.ts +33 -0
  84. package/src/app/create-edit-backup/create-edit-backup.state.ts +582 -0
  85. package/src/app/create-edit-backup/destination/destination.component.html +367 -0
  86. package/src/app/create-edit-backup/destination/destination.component.scss +85 -0
  87. package/src/app/create-edit-backup/destination/destination.component.ts +249 -0
  88. package/src/app/create-edit-backup/destination/destination.config.ts +103 -0
  89. package/src/app/create-edit-backup/destination/destination.mapper.ts +177 -0
  90. package/src/app/create-edit-backup/general/general.component.html +108 -0
  91. package/src/app/create-edit-backup/general/general.component.scss +34 -0
  92. package/src/app/create-edit-backup/general/general.component.ts +136 -0
  93. package/src/app/create-edit-backup/options/options.component.html +95 -0
  94. package/src/app/create-edit-backup/options/options.component.scss +42 -0
  95. package/src/app/create-edit-backup/options/options.component.ts +113 -0
  96. package/src/app/create-edit-backup/schedule/schedule.component.html +102 -0
  97. package/src/app/create-edit-backup/schedule/schedule.component.scss +38 -0
  98. package/src/app/create-edit-backup/schedule/schedule.component.ts +137 -0
  99. package/src/app/create-edit-backup/source-data/source-data.component.html +93 -0
  100. package/src/app/create-edit-backup/source-data/source-data.component.scss +41 -0
  101. package/src/app/create-edit-backup/source-data/source-data.component.ts +114 -0
  102. package/src/app/home/backup.const.ts +167 -0
  103. package/src/app/home/home.component.html +98 -0
  104. package/src/app/home/home.component.scss +88 -0
  105. package/src/app/home/home.component.ts +54 -0
  106. package/src/app/layout/layout.component.html +64 -0
  107. package/src/app/layout/layout.component.scss +24 -0
  108. package/src/app/layout/layout.component.ts +51 -0
  109. package/src/app/layout/layout.state.ts +70 -0
  110. package/src/app/login/login.component.html +12 -0
  111. package/src/app/login/login.component.scss +25 -0
  112. package/src/app/login/login.component.ts +34 -0
  113. package/src/app/logout/logout.component.html +1 -0
  114. package/src/app/logout/logout.component.scss +3 -0
  115. package/src/app/logout/logout.component.ts +18 -0
  116. package/src/app/restore/restore.component.html +56 -0
  117. package/src/app/restore/restore.component.scss +37 -0
  118. package/src/app/restore/restore.component.ts +55 -0
  119. package/src/app/restore-flow/options/options.component.html +57 -0
  120. package/src/app/restore-flow/options/options.component.scss +16 -0
  121. package/src/app/restore-flow/options/options.component.ts +58 -0
  122. package/src/app/restore-flow/restore-flow.component.html +14 -0
  123. package/src/app/restore-flow/restore-flow.component.scss +15 -0
  124. package/src/app/restore-flow/restore-flow.component.ts +23 -0
  125. package/src/app/restore-flow/restore-flow.state.ts +120 -0
  126. package/src/app/restore-flow/select-files/select-files.component.html +42 -0
  127. package/src/app/restore-flow/select-files/select-files.component.scss +16 -0
  128. package/src/app/restore-flow/select-files/select-files.component.ts +129 -0
  129. package/src/app/settings/settings.component.html +256 -0
  130. package/src/app/settings/settings.component.scss +106 -0
  131. package/src/app/settings/settings.component.ts +475 -0
  132. package/src/assets/duplicati-logo.png +0 -0
  133. package/src/assets/duplicati-logo.svg +10 -0
  134. package/src/assets/favicon.ico +0 -0
  135. package/src/assets/images/backup.png +0 -0
  136. package/src/assets/images/dark-theme.png +0 -0
  137. package/src/assets/images/folder.png +0 -0
  138. package/src/assets/images/light-theme.png +0 -0
  139. package/src/assets/images/new-backup.png +0 -0
  140. package/src/assets/images/restore.png +0 -0
  141. package/src/assets/images/system-theme.png +0 -0
  142. package/src/assets/spk.css +33 -0
  143. package/src/assets/spk.woff2 +0 -0
  144. package/src/environments/environment-token.ts +4 -0
  145. package/src/environments/environment.prod.ts +6 -0
  146. package/src/environments/environment.ts +6 -0
  147. package/src/index.html +15 -0
  148. package/src/main.ts +6 -0
  149. package/src/proxy.conf.mjs +6 -0
  150. package/src/styles.scss +41 -0
  151. package/tsconfig.app.json +15 -0
  152. 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
+ }