@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,582 @@
1
+ import { computed, inject, Injectable, signal } from '@angular/core';
2
+ import { toSignal } from '@angular/core/rxjs-interop';
3
+ import { FormArray, FormBuilder, Validators } from '@angular/forms';
4
+ import { Router } from '@angular/router';
5
+ import { finalize, take } from 'rxjs';
6
+ import {
7
+ ArgumentType,
8
+ BackupAndScheduleInputDto,
9
+ BackupDto,
10
+ DuplicatiServerService,
11
+ IDynamicModule,
12
+ ScheduleDto,
13
+ } from '../core/openapi';
14
+ import { TimespanLiteralsService } from '../core/services/timespan-literals.service';
15
+ import { SysinfoState } from '../core/states/sysinfo.state';
16
+ import { createDestinationForm, createDestinationFormGroup, Size } from './destination/destination.component';
17
+ import { DESTINATION_CONFIG, FormView } from './destination/destination.config';
18
+ import { DestinationFormGroup, fromTargetPath, toTargetPath } from './destination/destination.mapper';
19
+ import { createGeneralForm, NONE_OPTION } from './general/general.component';
20
+ import { createAdvancedOption, createOptionsForm } from './options/options.component';
21
+ import { createScheduleForm, Days, ScheduleFormValue } from './schedule/schedule.component';
22
+ import { createSourceDataForm } from './source-data/source-data.component';
23
+
24
+ const fb = new FormBuilder();
25
+
26
+ export type DestinationFormPair = {
27
+ oauthField: string | null;
28
+ custom: FormView[];
29
+ dynamic: FormView[];
30
+ advanced: FormView[];
31
+ };
32
+
33
+ type DestinationDefault = {
34
+ custom: {
35
+ [key: string]: any;
36
+ };
37
+ dynamic: {
38
+ [key: string]: any;
39
+ };
40
+ advanced: {
41
+ [key: string]: any;
42
+ };
43
+ };
44
+
45
+ @Injectable({
46
+ providedIn: 'root',
47
+ })
48
+ export class CreateEditBackupState {
49
+ #router = inject(Router);
50
+ #sysinfo = inject(SysinfoState);
51
+ #dupServer = inject(DuplicatiServerService);
52
+ #timespanLiteralService = inject(TimespanLiteralsService);
53
+ destinationOptions = this.#sysinfo.backendModules;
54
+
55
+ backupId = signal<'new' | 'string' | null>(null);
56
+ isSubmitting = signal(false);
57
+ loadingBackup = signal(false);
58
+ loadingDefaults = signal(true);
59
+ destinationIsLoaded = signal(false);
60
+ isNew = computed(() => this.backupId() === 'new');
61
+ backupDefaults = signal<any>(null);
62
+ generalForm = createGeneralForm();
63
+ sourceDataForm = createSourceDataForm();
64
+ scheduleForm = createScheduleForm();
65
+ destinationForm = createDestinationForm();
66
+ optionsForm = createOptionsForm();
67
+
68
+ selectedAdvancedFormPair = signal<FormView[]>([]);
69
+ notSelectedAdvancedFormPair = signal<FormView[]>([]);
70
+ destinationFormPair = signal<DestinationFormPair>({
71
+ oauthField: null,
72
+ custom: [],
73
+ dynamic: [],
74
+ advanced: [],
75
+ });
76
+ sourceDataFormSignal = toSignal(this.sourceDataForm.valueChanges);
77
+ destinationFormSignal = toSignal(this.destinationForm.valueChanges);
78
+ encryptionFieldSignal = toSignal(this.generalForm.controls.encryption.valueChanges);
79
+ scheduleFormSignal = toSignal(this.scheduleForm.valueChanges);
80
+ encryptionOptions = computed(() => {
81
+ const encryptionOptions = this.#sysinfo.systemInfo()?.EncryptionModules ?? [];
82
+ return [NONE_OPTION, ...encryptionOptions];
83
+ });
84
+
85
+ init(id: 'new' | 'string' = 'new') {
86
+ this.backupId.set(id);
87
+
88
+ if (id !== 'new') {
89
+ this.getBackup(id);
90
+ } else {
91
+ this.getDefaults();
92
+ }
93
+ }
94
+
95
+ submit() {
96
+ // TODOS
97
+ // - Create/update backup
98
+ this.isSubmitting.set(true);
99
+
100
+ const backup = this.#mapFormsToBackup();
101
+ const backupId = this.backupId();
102
+
103
+ if (backupId === 'new' || !backupId) {
104
+ this.#dupServer
105
+ .postApiV1Backups({
106
+ requestBody: backup,
107
+ })
108
+ .pipe(finalize(() => this.isSubmitting.set(false)))
109
+ .subscribe({
110
+ next: (res) => {
111
+ console.log('submitted res', res);
112
+
113
+ this.exit();
114
+ },
115
+ error: (err) => {
116
+ console.error('submitted error', err);
117
+ },
118
+ });
119
+ } else {
120
+ // TODO
121
+ // - Add ID to backup
122
+ // - Update backup
123
+ //
124
+ this.#dupServer
125
+ .putApiV1BackupById({
126
+ id: backupId,
127
+ requestBody: backup,
128
+ })
129
+ .pipe(finalize(() => this.isSubmitting.set(false)))
130
+ .subscribe({
131
+ next: (res) => {
132
+ console.log('submitted res', res);
133
+
134
+ this.exit();
135
+ },
136
+ error: (err) => {
137
+ console.error('submitted error', err);
138
+ },
139
+ });
140
+ }
141
+ }
142
+
143
+ exit() {
144
+ // TODOS
145
+ // - Are you sure dialog
146
+ this.#resetAllForms();
147
+ this.#router.navigate(['/']);
148
+ }
149
+
150
+ getDefaults() {
151
+ this.loadingDefaults.set(true);
152
+
153
+ this.#dupServer
154
+ .getApiV1Backupdefaults()
155
+ .pipe(
156
+ take(1),
157
+ finalize(() => this.loadingDefaults.set(false))
158
+ )
159
+ .subscribe({
160
+ next: (res: any) => {
161
+ this.#mapScheduleToForm(res.Schedule);
162
+ this.#mapOptionsToForms(res.Backup);
163
+ this.backupDefaults.set(res);
164
+ this.destinationIsLoaded.set(true);
165
+ },
166
+ });
167
+ }
168
+
169
+ getBackup(id: string) {
170
+ this.loadingBackup.set(true);
171
+
172
+ this.#dupServer
173
+ .getApiV1BackupById({
174
+ id,
175
+ })
176
+ .pipe(finalize(() => this.loadingBackup.set(false)))
177
+ .subscribe({
178
+ next: (res) => {
179
+ this.#mapScheduleToForm(res.Schedule ?? null);
180
+
181
+ if (res.Backup) {
182
+ this.#mapGeneralToForm(res.Backup);
183
+ this.#mapDestinationToForm(res.Backup);
184
+ this.#mapSourceDataToForm(res.Backup);
185
+ this.#mapOptionsToForms(res.Backup);
186
+ }
187
+
188
+ this.destinationIsLoaded.set(true);
189
+ },
190
+ });
191
+ }
192
+
193
+ #mapSourceDataToForm(backup: BackupDto) {
194
+ const path = backup.Sources ?? '';
195
+ const filters = backup.Filters?.map((x) => `${x.Include ? '' : '-'}${x.Expression}`) ?? [];
196
+ const excludes = backup.Settings?.find((x) => x.Name === '--exclude-files-attributes')?.Value ?? '';
197
+ const filesLargerThan = backup.Settings?.find((x) => x.Name === '--skip-files-larger-than') ?? null;
198
+
199
+ const withNoDigits = (filesLargerThan?.Value!.replace(/[0-9]/g, '') as Size) ?? null;
200
+ const onlyDigits = filesLargerThan?.Value!.replace(/[^0-9]/g, '') ?? null;
201
+
202
+ const sourceObj = {
203
+ path: [...path, ...filters].join('\0'),
204
+ excludes: {
205
+ hiddenFiles: excludes.includes('hidden'),
206
+ systemFiles: excludes.includes('system'),
207
+ tempFiles: excludes.includes('temporary'),
208
+ filesLargerThan:
209
+ withNoDigits && onlyDigits
210
+ ? {
211
+ size: parseInt(onlyDigits),
212
+ unit: withNoDigits.toUpperCase(),
213
+ }
214
+ : null,
215
+ },
216
+ };
217
+
218
+ this.sourceDataForm.patchValue(sourceObj as any);
219
+ }
220
+
221
+ #mapDestinationToForm(backup: BackupDto) {
222
+ const targetUrlData = backup.TargetURL ? fromTargetPath(backup.TargetURL) : null;
223
+
224
+ if (targetUrlData) {
225
+ this.addDestinationFormGroup(targetUrlData.destinationType, {
226
+ custom: targetUrlData.custom,
227
+ dynamic: targetUrlData.dynamic,
228
+ advanced: targetUrlData.advanced,
229
+ });
230
+ }
231
+ }
232
+
233
+ #mapGeneralToForm(backup: BackupDto) {
234
+ const encryptionModule = backup.Settings?.find((x) => x.Name === 'encryption-module');
235
+ const encryption = encryptionModule?.Value && encryptionModule.Value.length ? encryptionModule.Value : 'none';
236
+
237
+ this.generalForm.patchValue({
238
+ name: backup.Name ?? '',
239
+ description: backup.Description ?? '',
240
+ encryption,
241
+ });
242
+ }
243
+
244
+ #mapScheduleToForm(schedule: ScheduleDto | null) {
245
+ if (!schedule) {
246
+ this.scheduleForm.patchValue({
247
+ autoRun: true,
248
+ });
249
+
250
+ return;
251
+ }
252
+
253
+ const res = this.#timespanLiteralService.fromString(schedule.Repeat) ?? null;
254
+ const nextTime = this.#evaluateTimeString(schedule.Time);
255
+
256
+ const patchObj: ScheduleFormValue = {
257
+ nextTime,
258
+ runAgain: {
259
+ repeatValue: res?.value ?? 1,
260
+ repeatUnit: res?.unit ?? 'D',
261
+ allowedDays: {
262
+ mon: schedule.AllowedDays?.includes('mon') ?? false,
263
+ tue: schedule.AllowedDays?.includes('tue') ?? false,
264
+ wed: schedule.AllowedDays?.includes('wed') ?? false,
265
+ thu: schedule.AllowedDays?.includes('thu') ?? false,
266
+ fri: schedule.AllowedDays?.includes('fri') ?? false,
267
+ sat: schedule.AllowedDays?.includes('sat') ?? false,
268
+ sun: schedule.AllowedDays?.includes('sun') ?? false,
269
+ },
270
+ },
271
+ };
272
+
273
+ this.scheduleForm.patchValue(patchObj);
274
+ }
275
+
276
+ #mapOptionsToForms(backup: BackupDto) {
277
+ const modulesToIgnore = ['--no-encryption', '--exclude-files-attributes', '--skip-files-larger-than'];
278
+ backup.Settings?.forEach((x) => {
279
+ if (x.Name === 'encryption-module') {
280
+ return this.generalForm.controls.encryption.setValue(x.Value ?? 'None');
281
+ }
282
+
283
+ if (x.Name && modulesToIgnore.includes(x.Name)) return;
284
+
285
+ if (x.Name && x.Value) {
286
+ this.optionsForm.controls.advancedOptions.push(createAdvancedOption(x.Name, x.Value));
287
+ }
288
+ });
289
+ }
290
+
291
+ mapPub() {
292
+ this.#mapFormsToBackup();
293
+ }
294
+
295
+ #mapFormsToBackup() {
296
+ const generalFormValue = this.generalForm.value;
297
+ const optionsFormValue = this.optionsForm.value;
298
+ const scheduleFormValue = this.scheduleForm.value;
299
+ const sourceDataFormValue = this.sourceDataForm.value;
300
+ const destinationFormValue = this.destinationForm.value;
301
+
302
+ const targetUrl = destinationFormValue.destinations?.length
303
+ ? this.#getTargetUrl(this.destinationForm.controls.destinations as any)
304
+ : [];
305
+
306
+ let scheduleRepeat: string | null = null;
307
+
308
+ if (scheduleFormValue.runAgain?.repeatUnit && scheduleFormValue.runAgain?.repeatValue) {
309
+ scheduleRepeat = this.#timespanLiteralService.toString(
310
+ scheduleFormValue.runAgain.repeatValue,
311
+ scheduleFormValue.runAgain.repeatUnit
312
+ );
313
+ }
314
+
315
+ const allowedDays = scheduleFormValue.runAgain?.allowedDays
316
+ ? Object.entries(scheduleFormValue.runAgain?.allowedDays)
317
+ .filter(([_, status]) => status)
318
+ .map(([day, _]) => day as Days)
319
+ : [];
320
+
321
+ const pathFilters = sourceDataFormValue.path?.split('\0') ?? [];
322
+ const advancedOptions =
323
+ optionsFormValue.advancedOptions?.map((x) => ({
324
+ Name: x.name ?? null,
325
+ Value: x.value && x.value.length ? x.value.toString() : null,
326
+ })) ?? [];
327
+
328
+ const encryption =
329
+ generalFormValue.encryption === 'none'
330
+ ? {
331
+ Name: '--no-encryption',
332
+ Value: 'True',
333
+ }
334
+ : {
335
+ Name: 'encryption-module',
336
+ Value: generalFormValue.encryption ?? null,
337
+ };
338
+
339
+ const settings = [encryption, ...advancedOptions.filter((x) => x.Name && x.Value)];
340
+ const excludes = Object.entries(sourceDataFormValue.excludes ?? {})
341
+ .filter(([key, val]) => val && key !== 'filesLargerThan')
342
+ .map(([key]) => key);
343
+
344
+ const filesLargerThan = sourceDataFormValue.excludes?.filesLargerThan;
345
+
346
+ if (excludes.length) {
347
+ settings.push({
348
+ Name: '--exclude-files-attributes',
349
+ Value: excludes.join(','),
350
+ });
351
+ }
352
+ if (filesLargerThan && filesLargerThan.size !== null && filesLargerThan.unit !== null) {
353
+ settings.push({
354
+ Name: '--skip-files-larger-than',
355
+ Value: `${filesLargerThan.size}${filesLargerThan.unit}`,
356
+ });
357
+ }
358
+
359
+ return <BackupAndScheduleInputDto>{
360
+ Backup: {
361
+ Name: generalFormValue.name,
362
+ Description: generalFormValue.description,
363
+ TargetURL: targetUrl[0] ?? null,
364
+ Sources: pathFilters.filter((x) => !x.startsWith('-')),
365
+ Settings: settings,
366
+ Filters: pathFilters
367
+ .filter((x) => x.startsWith('-'))
368
+ .map((x, index) => ({
369
+ Order: index,
370
+ Include: false,
371
+ Expression: x.slice(1),
372
+ })),
373
+ },
374
+ Schedule: scheduleFormValue.autoRun
375
+ ? null
376
+ : {
377
+ Repeat: scheduleRepeat,
378
+ Time: scheduleFormValue.nextTime?.date
379
+ ? new Date(
380
+ `${scheduleFormValue.nextTime.date}T${scheduleFormValue.nextTime?.time || '00:00:00'}`
381
+ ).toISOString()
382
+ : null,
383
+ AllowedDays: allowedDays,
384
+ },
385
+ // ExtraOptions: {
386
+ // BackupID: this.backupId(),
387
+ // Operation: this.backupId() ? 'Update' : 'Create',
388
+ // },
389
+ };
390
+ }
391
+
392
+ #getTargetUrl(destinationFormArray: FormArray<DestinationFormGroup>) {
393
+ return destinationFormArray.controls.map((control) => {
394
+ if (!control.controls['advanced']) return toTargetPath(control.value);
395
+
396
+ const dirtyFields = Object.keys(control.controls['advanced'].controls).filter(
397
+ (controlName) => control.controls['advanced'].controls[controlName].dirty
398
+ );
399
+
400
+ return toTargetPath({
401
+ ...control.value,
402
+ advanced: dirtyFields.reduce((acc, key) => {
403
+ // @ts-ignore
404
+ acc[key] = control.controls['advanced'].controls[key].value;
405
+ return acc;
406
+ }, {}),
407
+ });
408
+ });
409
+ }
410
+
411
+ #evaluateTimeString(t: string | undefined) {
412
+ if (!t || t?.indexOf('T') === -1) {
413
+ return {
414
+ time: t ?? '13:00',
415
+ };
416
+ }
417
+
418
+ const date = new Date(t);
419
+ const newObj: ScheduleFormValue['nextTime'] = {
420
+ time: `${('' + date.getHours()).padStart(2, '0')}:${('' + date.getMinutes()).padStart(2, '0')}`,
421
+ };
422
+
423
+ if (t.indexOf('T') !== -1) {
424
+ newObj.date = date.toISOString().split('T')[0];
425
+ }
426
+
427
+ return newObj;
428
+ }
429
+
430
+ addDestinationFormGroup(key: IDynamicModule['Key'], defaults?: DestinationDefault) {
431
+ const item = this.destinationOptions().find((x) => x.Key === key);
432
+
433
+ if (!item || !item.Options) return;
434
+
435
+ const destinationConfig = DESTINATION_CONFIG.hasOwnProperty(key as string) && DESTINATION_CONFIG[key as string];
436
+ const customFields = destinationConfig && destinationConfig.customFields ? destinationConfig.customFields : {};
437
+ const dynamicFields = destinationConfig && destinationConfig.dynamicFields ? destinationConfig.dynamicFields : [];
438
+ const advancedFields =
439
+ destinationConfig && destinationConfig.advancedFields ? destinationConfig.advancedFields : [];
440
+ const oauthField = destinationConfig && destinationConfig.oauthField ? destinationConfig.oauthField : null;
441
+
442
+ this.destinationFormPair.set({
443
+ oauthField,
444
+ custom: [],
445
+ dynamic: [],
446
+ advanced: [],
447
+ });
448
+ this.selectedAdvancedFormPair.set([]);
449
+
450
+ const customGroup = fb.group({});
451
+ const dynamicGroup = fb.group({});
452
+ const advancedGroup = fb.group({});
453
+
454
+ if (customFields) {
455
+ for (key in customFields) {
456
+ const { formElement, ...val } = customFields[key];
457
+
458
+ customGroup.addControl(key, formElement(defaults?.custom[key]));
459
+
460
+ this.destinationFormPair.update((y) => {
461
+ y.custom.push(val);
462
+
463
+ return y;
464
+ });
465
+ }
466
+ }
467
+
468
+ for (let index = 0; index < item.Options.length; index++) {
469
+ const element = item.Options[index];
470
+
471
+ if (element.Deprecated) continue;
472
+
473
+ const isDynamic = dynamicFields.includes(element.Name as string);
474
+ const group = isDynamic ? dynamicGroup : advancedGroup;
475
+
476
+ this.destinationFormPair.update((y) => {
477
+ y[isDynamic ? 'dynamic' : 'advanced'].push({
478
+ name: element.Name as string,
479
+ type: element.Type as ArgumentType,
480
+ shortDescription: element.ShortDescription ?? undefined,
481
+ longDescription: element.LongDescription ?? undefined,
482
+ options: element.ValidValues,
483
+ });
484
+
485
+ return y;
486
+ });
487
+
488
+ const passedDefaultValue = isDynamic
489
+ ? defaults?.dynamic[element.Name as string]
490
+ : defaults?.advanced[element.Name as string];
491
+ const defaultValue = passedDefaultValue ?? element.DefaultValue;
492
+
493
+ if (
494
+ element.Type === 'String' ||
495
+ element.Type === 'Password' ||
496
+ element.Type === 'Enumeration' ||
497
+ element.Type === 'Path'
498
+ ) {
499
+ group.addControl(element.Name as string, fb.control(defaultValue));
500
+
501
+ continue;
502
+ }
503
+
504
+ if (element.Type === 'Size') {
505
+ const withNoDigits = defaultValue!.replace(/[0-9]/g, '') as Size | undefined;
506
+ const onlyDigits = defaultValue!.replace(/[^0-9]/g, '');
507
+
508
+ group.addControl(
509
+ element.Name as string,
510
+ fb.group({
511
+ size: fb.control<number>(onlyDigits ? parseInt(onlyDigits) : 50),
512
+ unit: fb.control<string>(withNoDigits ? withNoDigits.toUpperCase() : 'MB'),
513
+ })
514
+ );
515
+ }
516
+
517
+ if (element.Type === 'Integer') {
518
+ group.addControl(element.Name as string, fb.control<number>(defaultValue ? parseInt(defaultValue) : 0));
519
+
520
+ continue;
521
+ }
522
+
523
+ if (element.Type === 'Boolean') {
524
+ group.addControl(element.Name as string, fb.control(defaultValue === 'true'));
525
+
526
+ continue;
527
+ }
528
+
529
+ if (element.Type === 'Flags') {
530
+ group.addControl(element.Name as string, fb.control<string>(defaultValue ?? ''));
531
+
532
+ continue;
533
+ }
534
+
535
+ if (element.Type === 'Timespan') {
536
+ group.addControl(
537
+ element.Name as string,
538
+ fb.control<string>(defaultValue as string, [Validators.pattern(/([-+]?\d{1,3}[smhDWMY])+/)])
539
+ );
540
+ }
541
+ }
542
+
543
+ if (this.destinationForm.controls.destinations.length !== 0) {
544
+ this.destinationForm.controls.destinations.removeAt(0);
545
+ }
546
+
547
+ this.destinationFormPair.update((y) => ({
548
+ oauthField: y.oauthField,
549
+ custom: y.custom,
550
+ dynamic: y.dynamic.sort((a, b) => dynamicFields.indexOf(a.name) - dynamicFields.indexOf(b.name)),
551
+ advanced: y.advanced.sort((a, b) => {
552
+ const indexA = advancedFields.indexOf(a.name);
553
+ const indexB = advancedFields.indexOf(b.name);
554
+ if (indexA === -1 && indexB !== -1) {
555
+ return 1; // Move `a` to the back
556
+ } else if (indexA !== -1 && indexB === -1) {
557
+ return -1; // Move `b` to the back
558
+ }
559
+
560
+ return indexA - indexB;
561
+ }),
562
+ }));
563
+
564
+ this.notSelectedAdvancedFormPair.set(this.destinationFormPair().advanced);
565
+ this.destinationForm.controls.destinations.push(
566
+ createDestinationFormGroup({
567
+ key: item.Key as string,
568
+ customGroup,
569
+ dynamicGroup,
570
+ advancedGroup,
571
+ })
572
+ );
573
+ }
574
+
575
+ #resetAllForms() {
576
+ this.generalForm.reset();
577
+ this.sourceDataForm.reset();
578
+ this.scheduleForm.reset();
579
+ this.destinationForm.reset();
580
+ this.optionsForm.reset();
581
+ }
582
+ }