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';
20 SnapshotScheduleFormValue
21 } from '~/app/shared/models/snapshot-schedule';
22 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
24 const VALIDATON_TIMER = 300;
25 const DEBOUNCE_TIMER = 300;
28 selector: 'cd-cephfs-snapshotschedule-form',
29 templateUrl: './cephfs-snapshotschedule-form.component.html',
30 styleUrls: ['./cephfs-snapshotschedule-form.component.scss']
32 export class CephfsSnapshotscheduleFormComponent extends CdForm implements OnInit {
42 repeatFrequencies = Object.entries(RepeatFrequency);
43 retentionFrequencies = Object.entries(RetentionFrequency);
44 retentionPoliciesToRemove: RetentionPolicy[] = [];
46 currentTime!: NgbTimeStruct;
47 minDate!: NgbDateStruct;
49 snapScheduleForm!: CdFormGroup;
54 columns!: CdTableColumn[];
57 public activeModal: NgbActiveModal,
58 private actionLabels: ActionLabelsI18n,
59 private snapScheduleService: CephfsSnapshotScheduleService,
60 private taskWrapper: TaskWrapperService,
61 private cd: ChangeDetectorRef,
62 public directoryStore: DirectoryStoreService
65 this.resource = $localize`Snapshot schedule`;
67 const currentDatetime = new Date();
69 year: currentDatetime.getUTCFullYear(),
70 month: currentDatetime.getUTCMonth() + 1,
71 day: currentDatetime.getUTCDate()
74 hour: currentDatetime.getUTCHours(),
75 minute: currentDatetime.getUTCMinutes(),
76 second: currentDatetime.getUTCSeconds()
81 this.action = this.actionLabels.CREATE;
82 this.directoryStore.loadDirectories(this.id, '/', 3);
84 this.isEdit ? this.populateForm() : this.loadingReady();
87 get retentionPolicies() {
88 return this.snapScheduleForm.get('retentionPolicies') as FormArray;
91 search: OperatorFunction<string, readonly string[]> = (input: Observable<string>) =>
93 debounceTime(DEBOUNCE_TIMER),
94 distinctUntilChanged(),
96 this.directoryStore.search(term, this.id).pipe(
105 this.action = this.actionLabels.EDIT;
106 this.snapScheduleService.getSnapshotSchedule(this.path, this.fsName, false).subscribe({
107 next: (response: SnapshotSchedule[]) => {
108 const first = response.find((x) => x.path === this.path);
109 this.snapScheduleForm.get('directory').disable();
110 this.snapScheduleForm.get('directory').setValue(first.path);
111 this.snapScheduleForm.get('startDate').disable();
112 this.snapScheduleForm.get('startDate').setValue({
113 year: new Date(first.start).getUTCFullYear(),
114 month: new Date(first.start).getUTCMonth() + 1,
115 day: new Date(first.start).getUTCDate()
117 this.snapScheduleForm.get('startTime').disable();
118 this.snapScheduleForm.get('startTime').setValue({
119 hour: new Date(first.start).getUTCHours(),
120 minute: new Date(first.start).getUTCMinutes(),
121 second: new Date(first.start).getUTCSeconds()
123 this.snapScheduleForm.get('repeatInterval').disable();
124 this.snapScheduleForm.get('repeatInterval').setValue(first.schedule.split('')?.[0]);
125 this.snapScheduleForm.get('repeatFrequency').disable();
126 this.snapScheduleForm.get('repeatFrequency').setValue(first.schedule.split('')?.[1]);
128 // retention policies
130 Object.entries(first.retention).forEach(([frequency, interval], idx) => {
131 const freqKey = Object.keys(RetentionFrequency)[
132 Object.values(RetentionFrequency).indexOf(frequency as any)
134 this.retentionPolicies.push(
136 retentionInterval: new FormControl(interval),
137 retentionFrequency: new FormControl(RetentionFrequency[freqKey])
140 this.retentionPolicies.controls[idx].get('retentionInterval').disable();
141 this.retentionPolicies.controls[idx].get('retentionFrequency').disable();
149 this.snapScheduleForm = new CdFormGroup(
151 directory: new FormControl(undefined, {
152 validators: [Validators.required]
154 startDate: new FormControl(this.minDate, {
155 validators: [Validators.required]
157 startTime: new FormControl(this.currentTime, {
158 validators: [Validators.required]
160 repeatInterval: new FormControl(1, {
161 validators: [Validators.required, Validators.min(1)]
163 repeatFrequency: new FormControl(RepeatFrequency.Daily, {
164 validators: [Validators.required]
166 retentionPolicies: new FormArray([])
169 asyncValidators: [this.validateSchedule(), this.validateRetention()]
174 addRetentionPolicy() {
175 this.retentionPolicies.push(
177 retentionInterval: new FormControl(1),
178 retentionFrequency: new FormControl(RetentionFrequency.Daily)
181 this.cd.detectChanges();
184 removeRetentionPolicy(idx: number) {
185 if (this.isEdit && this.retentionPolicies.at(idx).disabled) {
186 const values = this.retentionPolicies.at(idx).value as RetentionPolicy;
187 this.retentionPoliciesToRemove.push(values);
189 this.retentionPolicies.removeAt(idx);
190 this.retentionPolicies.controls.forEach((x) =>
191 x.get('retentionFrequency').updateValueAndValidity()
193 this.cd.detectChanges();
196 parseDatetime(date: NgbDateStruct, time?: NgbTimeStruct): string {
197 if (!date || !time) return null;
198 return `${date.year}-${date.month}-${date.day}T${time.hour || '00'}:${time.minute || '00'}:${
202 parseSchedule(interval: number, frequency: string): string {
203 return `${interval}${frequency}`;
206 parseRetentionPolicies(retentionPolicies: RetentionPolicy[]) {
207 return retentionPolicies
208 ?.filter((r) => r?.retentionInterval !== null && r?.retentionFrequency !== null)
209 ?.map?.((r) => `${r.retentionInterval}-${r.retentionFrequency}`)
214 if (this.snapScheduleForm.invalid) {
215 this.snapScheduleForm.setErrors({ cdSubmitButton: true });
219 const values = this.snapScheduleForm.value as SnapshotScheduleFormValue;
222 const retentionPoliciesToAdd = (this.snapScheduleForm.get(
224 ) as FormArray).controls
227 !ctrl.get('retentionInterval').disabled && !ctrl.get('retentionFrequency').disabled
230 retentionInterval: ctrl.get('retentionInterval').value,
231 retentionFrequency: ctrl.get('retentionFrequency').value
237 retention_to_add: this.parseRetentionPolicies(retentionPoliciesToAdd) || null,
238 retention_to_remove: this.parseRetentionPolicies(this.retentionPoliciesToRemove) || null
242 .wrapTaskAroundCall({
243 task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.EDIT, {
246 call: this.snapScheduleService.update(updateObj)
250 this.snapScheduleForm.setErrors({ cdSubmitButton: true });
253 this.activeModal.close();
257 const snapScheduleObj = {
259 path: values.directory,
260 snap_schedule: this.parseSchedule(values?.repeatInterval, values?.repeatFrequency),
261 start: this.parseDatetime(values?.startDate, values?.startTime)
264 const retentionPoliciesValues = this.parseRetentionPolicies(values?.retentionPolicies);
265 if (retentionPoliciesValues) {
266 snapScheduleObj['retention_policy'] = retentionPoliciesValues;
269 .wrapTaskAroundCall({
270 task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.CREATE, {
271 path: snapScheduleObj.path
273 call: this.snapScheduleService.create(snapScheduleObj)
277 this.snapScheduleForm.setErrors({ cdSubmitButton: true });
280 this.activeModal.close();
287 return (frm: AbstractControl) => {
288 const directory = frm.get('directory');
289 const repeatFrequency = frm.get('repeatFrequency');
290 const repeatInterval = frm.get('repeatInterval');
296 return timer(VALIDATON_TIMER).pipe(
298 this.snapScheduleService
299 .checkScheduleExists(
302 repeatInterval?.value,
303 repeatFrequency?.value
306 map((exists: boolean) => {
308 repeatFrequency?.setErrors({ notUnique: true }, { emitEvent: true });
310 repeatFrequency?.setErrors(null);
320 getFormArrayItem(frm: FormGroup, frmArrayName: string, ctrl: string, idx: number) {
321 return (frm.get(frmArrayName) as FormArray)?.controls?.[idx]?.get?.(ctrl);
324 validateRetention() {
325 return (frm: FormGroup) => {
326 return timer(VALIDATON_TIMER).pipe(
328 const retentionList = (frm.get('retentionPolicies') as FormArray).controls?.map(
330 return ctrl.get('retentionFrequency').value;
333 if (uniq(retentionList)?.length !== retentionList?.length) {
334 this.getFormArrayItem(
337 'retentionFrequency',
338 retentionList.length - 1
344 return this.snapScheduleService
345 .checkRetentionPolicyExists(
346 frm.get('directory').value,
349 this.retentionPoliciesToRemove?.map?.((rp) => rp.retentionFrequency) || []
352 map(({ exists, errorIndex }) => {
354 this.getFormArrayItem(
357 'retentionFrequency',
359 )?.setErrors?.({ notUnique: true });
361 (frm.get('retentionPolicies') as FormArray).controls?.forEach?.((_, i) => {
362 this.getFormArrayItem(
365 'retentionFrequency',
367 )?.setErrors?.(null);