]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
41e55c72599ac62ee02e52e4f4c4bdddfe9733b6
[ceph.git] /
1 import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
2 import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
3 import { NgbActiveModal, NgbDateStruct, NgbTimeStruct } from '@ng-bootstrap/ng-bootstrap';
4 import { uniq } from 'lodash';
5 import { Observable, OperatorFunction, of, timer } from 'rxjs';
6 import { catchError, debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
7 import { CephfsSnapshotScheduleService } from '~/app/shared/api/cephfs-snapshot-schedule.service';
8 import { DirectoryStoreService } from '~/app/shared/api/directory-store.service';
9 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
10 import { Icons } from '~/app/shared/enum/icons.enum';
11 import { RepeatFrequency } from '~/app/shared/enum/repeat-frequency.enum';
12 import { RetentionFrequency } from '~/app/shared/enum/retention-frequency.enum';
13 import { CdForm } from '~/app/shared/forms/cd-form';
14 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
15 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
16 import { FinishedTask } from '~/app/shared/models/finished-task';
17 import { RetentionPolicy, SnapshotScheduleFormValue } from '~/app/shared/models/snapshot-schedule';
18 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
19
20 const VALIDATON_TIMER = 300;
21 const DEBOUNCE_TIMER = 300;
22
23 @Component({
24   selector: 'cd-cephfs-snapshotschedule-form',
25   templateUrl: './cephfs-snapshotschedule-form.component.html',
26   styleUrls: ['./cephfs-snapshotschedule-form.component.scss']
27 })
28 export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnInit {
29   fsName!: string;
30   id!: number;
31   isEdit = false;
32   icons = Icons;
33   repeatFrequencies = Object.entries(RepeatFrequency);
34   retentionFrequencies = Object.entries(RetentionFrequency);
35
36   currentTime!: NgbTimeStruct;
37   minDate!: NgbDateStruct;
38
39   snapScheduleForm!: CdFormGroup;
40
41   action!: string;
42   resource!: string;
43
44   columns!: CdTableColumn[];
45
46   constructor(
47     public activeModal: NgbActiveModal,
48     private actionLabels: ActionLabelsI18n,
49     private snapScheduleService: CephfsSnapshotScheduleService,
50     private taskWrapper: TaskWrapperService,
51     private cd: ChangeDetectorRef,
52     public directoryStore: DirectoryStoreService
53   ) {
54     super();
55     this.resource = $localize`Snapshot schedule`;
56
57     const currentDatetime = new Date();
58     this.minDate = {
59       year: currentDatetime.getUTCFullYear(),
60       month: currentDatetime.getUTCMonth() + 1,
61       day: currentDatetime.getUTCDate()
62     };
63     this.currentTime = {
64       hour: currentDatetime.getUTCHours(),
65       minute: currentDatetime.getUTCMinutes(),
66       second: currentDatetime.getUTCSeconds()
67     };
68   }
69
70   ngOnInit(): void {
71     this.action = this.actionLabels.CREATE;
72     this.directoryStore.loadDirectories(this.id, '/', 3);
73     this.createForm();
74     this.loadingReady();
75   }
76
77   get retentionPolicies() {
78     return this.snapScheduleForm.get('retentionPolicies') as FormArray;
79   }
80
81   search: OperatorFunction<string, readonly string[]> = (input: Observable<string>) =>
82     input.pipe(
83       debounceTime(DEBOUNCE_TIMER),
84       distinctUntilChanged(),
85       switchMap((term) =>
86         this.directoryStore.search(term, this.id).pipe(
87           catchError(() => {
88             return of([]);
89           })
90         )
91       )
92     );
93
94   createForm() {
95     this.snapScheduleForm = new CdFormGroup(
96       {
97         directory: new FormControl(undefined, {
98           validators: [Validators.required]
99         }),
100         startDate: new FormControl(this.minDate, {
101           validators: [Validators.required]
102         }),
103         startTime: new FormControl(this.currentTime, {
104           validators: [Validators.required]
105         }),
106         repeatInterval: new FormControl(1, {
107           validators: [Validators.required, Validators.min(1)]
108         }),
109         repeatFrequency: new FormControl(RepeatFrequency.Daily, {
110           validators: [Validators.required]
111         }),
112         retentionPolicies: new FormArray([])
113       },
114       {
115         asyncValidators: [this.validateSchedule(), this.validateRetention()]
116       }
117     );
118   }
119
120   addRetentionPolicy() {
121     this.retentionPolicies.push(
122       new FormGroup({
123         retentionInterval: new FormControl(1),
124         retentionFrequency: new FormControl(RetentionFrequency.Daily)
125       })
126     );
127     this.cd.detectChanges();
128   }
129
130   removeRetentionPolicy(idx: number) {
131     this.retentionPolicies.removeAt(idx);
132     this.cd.detectChanges();
133   }
134
135   parseDatetime(date: NgbDateStruct, time?: NgbTimeStruct): string {
136     return `${date.year}-${date.month}-${date.day}T${time.hour || '00'}:${time.minute || '00'}:${
137       time.second || '00'
138     }`;
139   }
140   parseSchedule(interval: number, frequency: string): string {
141     return `${interval}${frequency}`;
142   }
143
144   parseRetentionPolicies(retentionPolicies: RetentionPolicy[]) {
145     return retentionPolicies
146       ?.filter((r) => r?.retentionInterval !== null && r?.retentionFrequency !== null)
147       ?.map?.((r) => `${r.retentionInterval}-${r.retentionFrequency}`)
148       .join('|');
149   }
150
151   submit() {
152     if (this.snapScheduleForm.invalid) {
153       this.snapScheduleForm.setErrors({ cdSubmitButton: true });
154       return;
155     }
156
157     const values = this.snapScheduleForm.value as SnapshotScheduleFormValue;
158
159     const snapScheduleObj = {
160       fs: this.fsName,
161       path: values.directory,
162       snap_schedule: this.parseSchedule(values.repeatInterval, values.repeatFrequency),
163       start: this.parseDatetime(values.startDate, values.startTime)
164     };
165
166     const retentionPoliciesValues = this.parseRetentionPolicies(values?.retentionPolicies);
167     if (retentionPoliciesValues) {
168       snapScheduleObj['retention_policy'] = retentionPoliciesValues;
169     }
170
171     this.taskWrapper
172       .wrapTaskAroundCall({
173         task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.CREATE, {
174           path: snapScheduleObj.path
175         }),
176         call: this.snapScheduleService.create(snapScheduleObj)
177       })
178       .subscribe({
179         error: () => {
180           this.snapScheduleForm.setErrors({ cdSubmitButton: true });
181         },
182         complete: () => {
183           this.activeModal.close();
184         }
185       });
186   }
187
188   validateSchedule() {
189     return (frm: AbstractControl) => {
190       const directory = frm.get('directory');
191       const repeatFrequency = frm.get('repeatFrequency');
192       const repeatInterval = frm.get('repeatInterval');
193       return timer(VALIDATON_TIMER).pipe(
194         switchMap(() =>
195           this.snapScheduleService
196             .checkScheduleExists(
197               directory?.value,
198               this.fsName,
199               repeatInterval?.value,
200               repeatFrequency?.value
201             )
202             .pipe(
203               map((exists: boolean) => {
204                 if (exists) {
205                   repeatFrequency?.setErrors({ notUnique: true }, { emitEvent: true });
206                 } else {
207                   repeatFrequency?.setErrors(null);
208                 }
209                 return null;
210               })
211             )
212         )
213       );
214     };
215   }
216
217   getFormArrayItem(frm: FormGroup, frmArrayName: string, ctrl: string, idx: number) {
218     return (frm.get(frmArrayName) as FormArray)?.controls?.[idx]?.get?.(ctrl);
219   }
220
221   validateRetention() {
222     return (frm: FormGroup) => {
223       return timer(VALIDATON_TIMER).pipe(
224         switchMap(() => {
225           const retentionList = (frm.get('retentionPolicies') as FormArray).controls?.map(
226             (ctrl) => {
227               return ctrl.get('retentionFrequency').value;
228             }
229           );
230           if (uniq(retentionList)?.length !== retentionList?.length) {
231             this.getFormArrayItem(
232               frm,
233               'retentionPolicies',
234               'retentionFrequency',
235               retentionList.length - 1
236             )?.setErrors?.({
237               notUnique: true
238             });
239             return null;
240           }
241           return this.snapScheduleService
242             .checkRetentionPolicyExists(frm.get('directory').value, this.fsName, retentionList)
243             .pipe(
244               map(({ exists, errorIndex }) => {
245                 if (exists) {
246                   this.getFormArrayItem(
247                     frm,
248                     'retentionPolicies',
249                     'retentionFrequency',
250                     errorIndex
251                   )?.setErrors?.({ notUnique: true });
252                 } else {
253                   (frm.get('retentionPolicies') as FormArray).controls?.forEach?.((_, i) => {
254                     this.getFormArrayItem(
255                       frm,
256                       'retentionPolicies',
257                       'retentionFrequency',
258                       i
259                     )?.setErrors?.(null);
260                   });
261                 }
262                 return null;
263               })
264             );
265         })
266       );
267     };
268   }
269 }