]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / rgw / rgw-bucket-form / rgw-bucket-form.component.ts
1 import { AfterViewChecked, ChangeDetectorRef, Component, OnInit } from '@angular/core';
2 import { AbstractControl, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4
5 import _ from 'lodash';
6 import { forkJoin } from 'rxjs';
7
8 import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
9 import { RgwSiteService } from '~/app/shared/api/rgw-site.service';
10 import { RgwUserService } from '~/app/shared/api/rgw-user.service';
11 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
12 import { Icons } from '~/app/shared/enum/icons.enum';
13 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
14 import { CdForm } from '~/app/shared/forms/cd-form';
15 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
16 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
17 import { CdValidators } from '~/app/shared/forms/cd-validators';
18 import { ModalService } from '~/app/shared/services/modal.service';
19 import { NotificationService } from '~/app/shared/services/notification.service';
20 import { RgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
21 import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
22 import { RgwBucketVersioning } from '../models/rgw-bucket-versioning';
23 import { RgwConfigModalComponent } from '../rgw-config-modal/rgw-config-modal.component';
24
25 @Component({
26 selector: 'cd-rgw-bucket-form',
27 templateUrl: './rgw-bucket-form.component.html',
28 styleUrls: ['./rgw-bucket-form.component.scss'],
29 providers: [RgwBucketEncryptionModel]
30 })
31 export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewChecked {
32 bucketForm: CdFormGroup;
33 editing = false;
34 owners: string[] = null;
35 kmsProviders: string[] = null;
36 action: string;
37 resource: string;
38 zonegroup: string;
39 placementTargets: object[] = [];
40 isVersioningAlreadyEnabled = false;
41 isMfaDeleteAlreadyEnabled = false;
42 icons = Icons;
43 kmsVaultConfig = false;
44 s3VaultConfig = false;
45
46 get isVersioningEnabled(): boolean {
47 return this.bucketForm.getValue('versioning');
48 }
49 get isMfaDeleteEnabled(): boolean {
50 return this.bucketForm.getValue('mfa-delete');
51 }
52
53 constructor(
54 private route: ActivatedRoute,
55 private router: Router,
56 private formBuilder: CdFormBuilder,
57 private rgwBucketService: RgwBucketService,
58 private rgwSiteService: RgwSiteService,
59 private modalService: ModalService,
60 private rgwUserService: RgwUserService,
61 private notificationService: NotificationService,
62 private rgwEncryptionModal: RgwBucketEncryptionModel,
63 public actionLabels: ActionLabelsI18n,
64 private readonly changeDetectorRef: ChangeDetectorRef
65 ) {
66 super();
67 this.editing = this.router.url.startsWith(`/rgw/bucket/${URLVerbs.EDIT}`);
68 this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
69 this.resource = $localize`bucket`;
70 this.createForm();
71 }
72
73 ngAfterViewChecked(): void {
74 this.changeDetectorRef.detectChanges();
75 }
76
77 createForm() {
78 const self = this;
79 const lockDaysValidator = CdValidators.custom('lockDays', () => {
80 if (!self.bucketForm || !_.get(self.bucketForm.getRawValue(), 'lock_enabled')) {
81 return false;
82 }
83 const lockDays = Number(self.bucketForm.getValue('lock_retention_period_days'));
84 return !Number.isInteger(lockDays) || lockDays === 0;
85 });
86 this.bucketForm = this.formBuilder.group({
87 id: [null],
88 bid: [
89 null,
90 [Validators.required],
91 this.editing
92 ? []
93 : [CdValidators.bucketName(), CdValidators.bucketExistence(false, this.rgwBucketService)]
94 ],
95 owner: [null, [Validators.required]],
96 kms_provider: ['vault'],
97 'placement-target': [null, this.editing ? [] : [Validators.required]],
98 versioning: [null],
99 'mfa-delete': [null],
100 'mfa-token-serial': [''],
101 'mfa-token-pin': [''],
102 lock_enabled: [{ value: false, disabled: this.editing }],
103 encryption_enabled: [null],
104 encryption_type: [
105 null,
106 [
107 CdValidators.requiredIf({
108 encryption_enabled: true
109 })
110 ]
111 ],
112 keyId: [
113 null,
114 [
115 CdValidators.requiredIf({
116 encryption_type: 'aws:kms',
117 encryption_enabled: true
118 })
119 ]
120 ],
121 lock_mode: ['COMPLIANCE'],
122 lock_retention_period_days: [0, [CdValidators.number(false), lockDaysValidator]]
123 });
124 }
125
126 ngOnInit() {
127 const promises = {
128 owners: this.rgwUserService.enumerate()
129 };
130
131 this.kmsProviders = this.rgwEncryptionModal.kmsProviders;
132 this.rgwBucketService.getEncryptionConfig().subscribe((data) => {
133 this.kmsVaultConfig = data[0];
134 this.s3VaultConfig = data[1];
135 if (this.kmsVaultConfig && this.s3VaultConfig) {
136 this.bucketForm.get('encryption_type').setValue('');
137 } else if (this.kmsVaultConfig) {
138 this.bucketForm.get('encryption_type').setValue('aws:kms');
139 } else if (this.s3VaultConfig) {
140 this.bucketForm.get('encryption_type').setValue('AES256');
141 } else {
142 this.bucketForm.get('encryption_type').setValue('');
143 }
144 });
145
146 if (!this.editing) {
147 promises['getPlacementTargets'] = this.rgwSiteService.get('placement-targets');
148 }
149
150 // Process route parameters.
151 this.route.params.subscribe((params: { bid: string }) => {
152 if (params.hasOwnProperty('bid')) {
153 const bid = decodeURIComponent(params.bid);
154 promises['getBid'] = this.rgwBucketService.get(bid);
155 }
156
157 forkJoin(promises).subscribe((data: any) => {
158 // Get the list of possible owners.
159 this.owners = (<string[]>data.owners).sort();
160
161 // Get placement targets:
162 if (data['getPlacementTargets']) {
163 const placementTargets = data['getPlacementTargets'];
164 this.zonegroup = placementTargets['zonegroup'];
165 _.forEach(placementTargets['placement_targets'], (placementTarget) => {
166 placementTarget['description'] = `${placementTarget['name']} (${$localize`pool`}: ${
167 placementTarget['data_pool']
168 })`;
169 this.placementTargets.push(placementTarget);
170 });
171
172 // If there is only 1 placement target, select it by default:
173 if (this.placementTargets.length === 1) {
174 this.bucketForm.get('placement-target').setValue(this.placementTargets[0]['name']);
175 }
176 }
177
178 if (data['getBid']) {
179 const bidResp = data['getBid'];
180 // Get the default values (incl. the values from disabled fields).
181 const defaults = _.clone(this.bucketForm.getRawValue());
182
183 // Get the values displayed in the form. We need to do that to
184 // extract those key/value pairs from the response data, otherwise
185 // the Angular react framework will throw an error if there is no
186 // field for a given key.
187 let value: object = _.pick(bidResp, _.keys(defaults));
188
189 value['lock_retention_period_days'] = this.rgwBucketService.getLockDays(bidResp);
190 value['placement-target'] = bidResp['placement_rule'];
191 value['versioning'] = bidResp['versioning'] === RgwBucketVersioning.ENABLED;
192 value['mfa-delete'] = bidResp['mfa_delete'] === RgwBucketMfaDelete.ENABLED;
193 value['encryption_enabled'] = bidResp['encryption'] === 'Enabled';
194 // Append default values.
195 value = _.merge(defaults, value);
196 // Update the form.
197 this.bucketForm.setValue(value);
198 if (this.editing) {
199 this.isVersioningAlreadyEnabled = this.isVersioningEnabled;
200 this.isMfaDeleteAlreadyEnabled = this.isMfaDeleteEnabled;
201 this.setMfaDeleteValidators();
202 if (value['lock_enabled']) {
203 this.bucketForm.controls['versioning'].disable();
204 }
205 }
206 }
207 this.loadingReady();
208 });
209 });
210 }
211
212 goToListView() {
213 this.router.navigate(['/rgw/bucket']);
214 }
215
216 submit() {
217 // Exit immediately if the form isn't dirty.
218 if (this.bucketForm.getValue('encryption_enabled') == null) {
219 this.bucketForm.get('encryption_enabled').setValue(false);
220 this.bucketForm.get('encryption_type').setValue(null);
221 }
222 if (this.bucketForm.pristine) {
223 this.goToListView();
224 return;
225 }
226 const values = this.bucketForm.value;
227 if (this.editing) {
228 // Edit
229 const versioning = this.getVersioningStatus();
230 const mfaDelete = this.getMfaDeleteStatus();
231 this.rgwBucketService
232 .update(
233 values['bid'],
234 values['id'],
235 values['owner'],
236 versioning,
237 values['encryption_enabled'],
238 values['encryption_type'],
239 values['keyId'],
240 mfaDelete,
241 values['mfa-token-serial'],
242 values['mfa-token-pin'],
243 values['lock_mode'],
244 values['lock_retention_period_days']
245 )
246 .subscribe(
247 () => {
248 this.notificationService.show(
249 NotificationType.success,
250 $localize`Updated Object Gateway bucket '${values.bid}'.`
251 );
252 this.goToListView();
253 },
254 () => {
255 // Reset the 'Submit' button.
256 this.bucketForm.setErrors({ cdSubmitButton: true });
257 }
258 );
259 } else {
260 // Add
261 this.rgwBucketService
262 .create(
263 values['bid'],
264 values['owner'],
265 this.zonegroup,
266 values['placement-target'],
267 values['lock_enabled'],
268 values['lock_mode'],
269 values['lock_retention_period_days'],
270 values['encryption_enabled'],
271 values['encryption_type'],
272 values['keyId']
273 )
274 .subscribe(
275 () => {
276 this.notificationService.show(
277 NotificationType.success,
278 $localize`Created Object Gateway bucket '${values.bid}'`
279 );
280 this.goToListView();
281 },
282 () => {
283 // Reset the 'Submit' button.
284 this.bucketForm.setErrors({ cdSubmitButton: true });
285 }
286 );
287 }
288 }
289
290 areMfaCredentialsRequired() {
291 return (
292 this.isMfaDeleteEnabled !== this.isMfaDeleteAlreadyEnabled ||
293 (this.isMfaDeleteAlreadyEnabled &&
294 this.isVersioningEnabled !== this.isVersioningAlreadyEnabled)
295 );
296 }
297
298 setMfaDeleteValidators() {
299 const mfaTokenSerialControl = this.bucketForm.get('mfa-token-serial');
300 const mfaTokenPinControl = this.bucketForm.get('mfa-token-pin');
301
302 if (this.areMfaCredentialsRequired()) {
303 mfaTokenSerialControl.setValidators(Validators.required);
304 mfaTokenPinControl.setValidators(Validators.required);
305 } else {
306 mfaTokenSerialControl.setValidators(null);
307 mfaTokenPinControl.setValidators(null);
308 }
309
310 mfaTokenSerialControl.updateValueAndValidity();
311 mfaTokenPinControl.updateValueAndValidity();
312 }
313
314 getVersioningStatus() {
315 return this.isVersioningEnabled ? RgwBucketVersioning.ENABLED : RgwBucketVersioning.SUSPENDED;
316 }
317
318 getMfaDeleteStatus() {
319 return this.isMfaDeleteEnabled ? RgwBucketMfaDelete.ENABLED : RgwBucketMfaDelete.DISABLED;
320 }
321
322 fileUpload(files: FileList, controlName: string) {
323 const file: File = files[0];
324 const reader = new FileReader();
325 reader.addEventListener('load', () => {
326 const control: AbstractControl = this.bucketForm.get(controlName);
327 control.setValue(file);
328 control.markAsDirty();
329 control.markAsTouched();
330 control.updateValueAndValidity();
331 });
332 }
333
334 openConfigModal() {
335 const modalRef = this.modalService.show(RgwConfigModalComponent, null, { size: 'lg' });
336 modalRef.componentInstance.configForm
337 .get('encryptionType')
338 .setValue(this.bucketForm.getValue('encryption_type') || 'AES256');
339 }
340 }