]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
dde30ad0e94734c2ce2ab661c8fde102f62d950f
[ceph-ci.git] /
1 import { ChangeDetectorRef, Component, Inject, OnInit, Optional } from '@angular/core';
2 import {
3   AbstractControl,
4   FormArray,
5   FormControl,
6   FormGroup,
7   ValidationErrors,
8   Validators
9 } from '@angular/forms';
10 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
11 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
12 import { CdValidators } from '~/app/shared/forms/cd-validators';
13 import { Bucket } from '../models/rgw-bucket';
14 import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
15 import { NotificationService } from '~/app/shared/services/notification.service';
16 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
17 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
18 import { BucketTieringUtils } from '../utils/rgw-bucket-tiering';
19 import { StorageClass, ZoneGroupDetails } from '../models/rgw-storage-class.model';
20 import { CdForm } from '~/app/shared/forms/cd-form';
21 import { Router } from '@angular/router';
22 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
23 import { Lifecycle, Rule, Tag } from '../models/rgw-bucket-lifecycle';
24
25 export interface Tags {
26   tagKey: number;
27   tagValue: string;
28 }
29
30 @Component({
31   selector: 'cd-rgw-bucket-tiering',
32   templateUrl: './rgw-bucket-tiering-form.component.html',
33   styleUrls: ['./rgw-bucket-tiering-form.component.scss']
34 })
35 export class RgwBucketTieringFormComponent extends CdForm implements OnInit {
36   tieringForm: CdFormGroup;
37   tagsToRemove: Tags[] = [];
38   storageClassList: StorageClass[] = null;
39   configuredLifecycle: Lifecycle;
40   isStorageClassFetched = false;
41
42   constructor(
43     @Inject('bucket') public bucket: Bucket,
44     @Optional() @Inject('selectedLifecycle') public selectedLifecycle: Rule,
45     @Optional() @Inject('editing') public editing = false,
46     public actionLabels: ActionLabelsI18n,
47     private rgwBucketService: RgwBucketService,
48     private fb: CdFormBuilder,
49     private cd: ChangeDetectorRef,
50     private rgwZonegroupService: RgwZonegroupService,
51     private notificationService: NotificationService,
52     private router: Router
53   ) {
54     super();
55   }
56
57   ngOnInit() {
58     this.rgwBucketService
59       .getLifecycle(this.bucket.bucket, this.bucket.owner, this.bucket.tenant)
60       .subscribe((lifecycle: Lifecycle) => {
61         if (lifecycle === null) {
62           lifecycle = { LifecycleConfiguration: { Rule: [] } };
63         }
64         lifecycle.LifecycleConfiguration.Rule = lifecycle.LifecycleConfiguration.Rule.map(
65           (rule: Rule) => {
66             if (rule?.['Filter']?.['Tag'] && !Array.isArray(rule?.['Filter']?.['Tag'])) {
67               rule['Filter']['Tag'] = [rule['Filter']['Tag']];
68             }
69             if (
70               rule?.['Filter']?.['And']?.['Tag'] &&
71               !Array.isArray(rule?.['Filter']?.['And']?.['Tag'])
72             ) {
73               rule['Filter']['And']['Tag'] = [rule['Filter']['And']['Tag']];
74             }
75             return rule;
76           }
77         );
78         this.configuredLifecycle = lifecycle;
79         if (this.editing) {
80           const ruleToEdit = this.configuredLifecycle?.['LifecycleConfiguration']?.['Rule'].filter(
81             (rule: Rule) => rule?.['ID'] === this.selectedLifecycle?.['ID']
82           )[0];
83           this.tieringForm.patchValue({
84             name: ruleToEdit?.['ID'],
85             hasPrefix: this.checkIfRuleHasFilters(ruleToEdit),
86             prefix:
87               ruleToEdit?.['Prefix'] ||
88               ruleToEdit?.['Filter']?.['Prefix'] ||
89               ruleToEdit?.['Filter']?.['And']?.['Prefix'] ||
90               '',
91             status: ruleToEdit?.['Status'],
92             days: ruleToEdit?.['Transition']?.['Days']
93           });
94           this.setTags(ruleToEdit);
95           this.tieringForm.get('name').disable();
96         }
97       });
98     this.tieringForm = this.fb.group({
99       name: [null, [Validators.required, this.duplicateConfigName.bind(this)]],
100       storageClass: [null, Validators.required],
101       hasPrefix: [false, [Validators.required]],
102       prefix: [null, [CdValidators.composeIf({ hasPrefix: true }, [Validators.required])]],
103       tags: this.fb.array([]),
104       status: ['Enabled', [Validators.required]],
105       days: [60, [Validators.required, CdValidators.number(false)]]
106     });
107     this.loadStorageClass();
108   }
109
110   checkIfRuleHasFilters(rule: Rule) {
111     if (
112       this.isValidPrefix(rule?.['Prefix']) ||
113       this.isValidPrefix(rule?.['Filter']?.['Prefix']) ||
114       this.isValidArray(rule?.['Filter']?.['Tag']) ||
115       this.isValidPrefix(rule?.['Filter']?.['And']?.['Prefix']) ||
116       this.isValidArray(rule?.['Filter']?.['And']?.['Tag'])
117     ) {
118       return true;
119     }
120     return false;
121   }
122
123   isValidPrefix(value: string) {
124     return !!value;
125   }
126
127   isValidArray(value: Tag | Tag[]) {
128     return Array.isArray(value) && value.length > 0;
129   }
130
131   setTags(rule: Rule) {
132     if (Array.isArray(rule?.Filter?.Tag) && rule?.Filter?.Tag?.length > 0) {
133       rule?.['Filter']?.['Tag']?.forEach((tag: { Key: string; Value: string }) =>
134         this.addTags(tag.Key, tag.Value)
135       );
136     }
137     if (Array.isArray(rule?.Filter?.And?.Tag) && rule?.Filter?.And?.Tag?.length > 0) {
138       rule?.['Filter']?.['And']?.['Tag']?.forEach((tag: { Key: string; Value: string }) =>
139         this.addTags(tag.Key, tag.Value)
140       );
141     }
142   }
143
144   get tags() {
145     return this.tieringForm.get('tags') as FormArray;
146   }
147
148   addTags(key?: string, value?: string) {
149     this.tags.push(
150       new FormGroup({
151         Key: new FormControl(key || '', Validators.required),
152         Value: new FormControl(value || '', Validators.required)
153       })
154     );
155     this.cd.detectChanges();
156   }
157
158   duplicateConfigName(control: AbstractControl): ValidationErrors | null {
159     if (this.configuredLifecycle?.LifecycleConfiguration?.Rule?.length > 0) {
160       const ruleIds = this.configuredLifecycle.LifecycleConfiguration.Rule.map(
161         (rule: Rule) => rule.ID
162       );
163       return ruleIds.includes(control.value) ? { duplicate: true } : null;
164     }
165     return null;
166   }
167
168   removeTags(idx: number) {
169     this.tags.removeAt(idx);
170     this.cd.detectChanges();
171   }
172
173   loadStorageClass(): Promise<void> {
174     return new Promise((resolve, reject) => {
175       this.rgwZonegroupService.getAllZonegroupsInfo().subscribe(
176         (data: ZoneGroupDetails) => {
177           this.storageClassList = [];
178           const tierObj = BucketTieringUtils.filterAndMapTierTargets(data);
179           this.isStorageClassFetched = true;
180           this.storageClassList.push(...tierObj);
181           if (this.editing) {
182             this.tieringForm
183               .get('storageClass')
184               .setValue(this.selectedLifecycle?.['Transition']?.['StorageClass']);
185           }
186           this.loadingReady();
187           resolve();
188         },
189         (error) => {
190           reject(error);
191         }
192       );
193     });
194   }
195
196   submitTieringConfig() {
197     const formValue = this.tieringForm.value;
198     if (!this.tieringForm.valid) {
199       return;
200     }
201
202     let lifecycle: Rule = {
203       ID: this.tieringForm.getRawValue().name,
204       Status: formValue.status,
205       Transition: {
206         Days: formValue.days,
207         StorageClass: formValue.storageClass
208       }
209     };
210     if (formValue.hasPrefix) {
211       if (this.tags.length > 0) {
212         Object.assign(lifecycle, {
213           Filter: {
214             And: {
215               Prefix: formValue.prefix,
216               Tag: this.tags.value
217             }
218           }
219         });
220       } else {
221         Object.assign(lifecycle, {
222           Filter: {
223             Prefix: formValue.prefix
224           }
225         });
226       }
227     } else {
228       Object.assign(lifecycle, {
229         Prefix: ''
230       });
231     }
232     if (!this.editing) {
233       this.configuredLifecycle.LifecycleConfiguration.Rule.push(lifecycle);
234       this.rgwBucketService
235         .setLifecycle(
236           this.bucket.bucket,
237           JSON.stringify(this.configuredLifecycle.LifecycleConfiguration),
238           this.bucket.owner,
239           this.bucket.tenant
240         )
241         .subscribe({
242           next: () => {
243             this.notificationService.show(
244               NotificationType.success,
245               $localize`Bucket lifecycle created succesfully`
246             );
247           },
248           error: (error: any) => {
249             this.notificationService.show(NotificationType.error, error);
250             this.tieringForm.setErrors({ cdSubmitButton: true });
251           },
252           complete: () => {
253             this.closeModal();
254           }
255         });
256     } else {
257       const rules = this.configuredLifecycle.LifecycleConfiguration.Rule;
258       const index = rules.findIndex(
259         (rule: Rule) => rule?.['ID'] === this.selectedLifecycle?.['ID']
260       );
261       rules.splice(index, 1, lifecycle);
262       this.rgwBucketService
263         .setLifecycle(
264           this.bucket.bucket,
265           JSON.stringify(this.configuredLifecycle.LifecycleConfiguration),
266           this.bucket.owner,
267           this.bucket.tenant
268         )
269         .subscribe({
270           next: () => {
271             this.notificationService.show(
272               NotificationType.success,
273               $localize`Bucket lifecycle modified succesfully`
274             );
275           },
276           error: (error: any) => {
277             this.notificationService.show(NotificationType.error, error);
278             this.tieringForm.setErrors({ cdSubmitButton: true });
279           },
280           complete: () => {
281             this.closeModal();
282           }
283         });
284     }
285   }
286
287   goToCreateStorageClass() {
288     this.router.navigate(['rgw/tiering/create']);
289     this.closeModal();
290   }
291 }