]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / rgw / rgw-user-form / rgw-user-form.component.ts
CommitLineData
11fdf7f2
TL
1import { Component, OnInit } from '@angular/core';
2import { AbstractControl, ValidationErrors, Validators } from '@angular/forms';
3import { ActivatedRoute, Router } from '@angular/router';
4
f67539c2 5import _ from 'lodash';
92f5a8d4 6import { concat as observableConcat, forkJoin as observableForkJoin, Observable } from 'rxjs';
11fdf7f2 7
f67539c2
TL
8import { RgwUserService } from '~/app/shared/api/rgw-user.service';
9import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
10import { Icons } from '~/app/shared/enum/icons.enum';
11import { NotificationType } from '~/app/shared/enum/notification-type.enum';
12import { CdForm } from '~/app/shared/forms/cd-form';
13import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
14import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
15import { CdValidators, isEmptyInputValue } from '~/app/shared/forms/cd-validators';
16import { FormatterService } from '~/app/shared/services/formatter.service';
17import { ModalService } from '~/app/shared/services/modal.service';
18import { NotificationService } from '~/app/shared/services/notification.service';
92f5a8d4 19import { RgwUserCapabilities } from '../models/rgw-user-capabilities';
11fdf7f2
TL
20import { RgwUserCapability } from '../models/rgw-user-capability';
21import { RgwUserS3Key } from '../models/rgw-user-s3-key';
22import { RgwUserSubuser } from '../models/rgw-user-subuser';
23import { RgwUserSwiftKey } from '../models/rgw-user-swift-key';
24import { RgwUserCapabilityModalComponent } from '../rgw-user-capability-modal/rgw-user-capability-modal.component';
25import { RgwUserS3KeyModalComponent } from '../rgw-user-s3-key-modal/rgw-user-s3-key-modal.component';
26import { RgwUserSubuserModalComponent } from '../rgw-user-subuser-modal/rgw-user-subuser-modal.component';
27import { RgwUserSwiftKeyModalComponent } from '../rgw-user-swift-key-modal/rgw-user-swift-key-modal.component';
28
29@Component({
30 selector: 'cd-rgw-user-form',
31 templateUrl: './rgw-user-form.component.html',
32 styleUrls: ['./rgw-user-form.component.scss']
33})
f67539c2 34export class RgwUserFormComponent extends CdForm implements OnInit {
11fdf7f2
TL
35 userForm: CdFormGroup;
36 editing = false;
11fdf7f2 37 submitObservables: Observable<Object>[] = [];
9f95a23c 38 icons = Icons;
11fdf7f2
TL
39 subusers: RgwUserSubuser[] = [];
40 s3Keys: RgwUserS3Key[] = [];
41 swiftKeys: RgwUserSwiftKey[] = [];
42 capabilities: RgwUserCapability[] = [];
43
44 action: string;
45 resource: string;
46 subuserLabel: string;
47 s3keyLabel: string;
48 capabilityLabel: string;
f67539c2
TL
49 usernameExists: boolean;
50 showTenant = false;
51 previousTenant: string = null;
11fdf7f2
TL
52
53 constructor(
54 private formBuilder: CdFormBuilder,
55 private route: ActivatedRoute,
56 private router: Router,
57 private rgwUserService: RgwUserService,
f67539c2 58 private modalService: ModalService,
11fdf7f2 59 private notificationService: NotificationService,
11fdf7f2
TL
60 public actionLabels: ActionLabelsI18n
61 ) {
f67539c2
TL
62 super();
63 this.resource = $localize`user`;
64 this.subuserLabel = $localize`subuser`;
65 this.s3keyLabel = $localize`S3 Key`;
66 this.capabilityLabel = $localize`capability`;
adb31ebb
TL
67 this.editing = this.router.url.startsWith(`/rgw/user/${URLVerbs.EDIT}`);
68 this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
11fdf7f2 69 this.createForm();
11fdf7f2
TL
70 }
71
72 createForm() {
73 this.userForm = this.formBuilder.group({
74 // General
f67539c2 75 user_id: [
11fdf7f2 76 null,
f67539c2
TL
77 [Validators.required, Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)],
78 this.editing
79 ? []
80 : [
81 CdValidators.unique(this.rgwUserService.exists, this.rgwUserService, () =>
82 this.userForm.getValue('tenant')
83 )
84 ]
85 ],
86 show_tenant: [this.editing],
87 tenant: [
88 null,
89 [Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)],
90 this.editing
91 ? []
92 : [
93 CdValidators.unique(
94 this.rgwUserService.exists,
95 this.rgwUserService,
96 () => this.userForm.getValue('user_id'),
97 true
98 )
99 ]
11fdf7f2
TL
100 ],
101 display_name: [null, [Validators.required]],
102 email: [
103 null,
104 [CdValidators.email],
105 [CdValidators.unique(this.rgwUserService.emailExists, this.rgwUserService)]
106 ],
9f95a23c
TL
107 max_buckets_mode: [1],
108 max_buckets: [
109 1000,
20effc67 110 [CdValidators.requiredIf({ max_buckets_mode: '1' }), CdValidators.number(false)]
9f95a23c 111 ],
11fdf7f2
TL
112 suspended: [false],
113 // S3 key
114 generate_key: [true],
115 access_key: [null, [CdValidators.requiredIf({ generate_key: false })]],
116 secret_key: [null, [CdValidators.requiredIf({ generate_key: false })]],
117 // User quota
118 user_quota_enabled: [false],
119 user_quota_max_size_unlimited: [true],
120 user_quota_max_size: [
121 null,
122 [
9f95a23c
TL
123 CdValidators.composeIf(
124 {
125 user_quota_enabled: true,
126 user_quota_max_size_unlimited: false
127 },
128 [Validators.required, this.quotaMaxSizeValidator]
129 )
11fdf7f2
TL
130 ]
131 ],
132 user_quota_max_objects_unlimited: [true],
133 user_quota_max_objects: [
134 null,
135 [
11fdf7f2
TL
136 CdValidators.requiredIf({
137 user_quota_enabled: true,
138 user_quota_max_objects_unlimited: false
139 })
140 ]
141 ],
142 // Bucket quota
143 bucket_quota_enabled: [false],
144 bucket_quota_max_size_unlimited: [true],
145 bucket_quota_max_size: [
146 null,
147 [
9f95a23c
TL
148 CdValidators.composeIf(
149 {
150 bucket_quota_enabled: true,
151 bucket_quota_max_size_unlimited: false
152 },
153 [Validators.required, this.quotaMaxSizeValidator]
154 )
11fdf7f2
TL
155 ]
156 ],
157 bucket_quota_max_objects_unlimited: [true],
158 bucket_quota_max_objects: [
159 null,
160 [
11fdf7f2
TL
161 CdValidators.requiredIf({
162 bucket_quota_enabled: true,
163 bucket_quota_max_objects_unlimited: false
164 })
165 ]
166 ]
167 });
168 }
169
11fdf7f2 170 ngOnInit() {
11fdf7f2
TL
171 // Process route parameters.
172 this.route.params.subscribe((params: { uid: string }) => {
173 if (!params.hasOwnProperty('uid')) {
f67539c2 174 this.loadingReady();
11fdf7f2
TL
175 return;
176 }
177 const uid = decodeURIComponent(params.uid);
11fdf7f2
TL
178 // Load the user and quota information.
179 const observables = [];
180 observables.push(this.rgwUserService.get(uid));
181 observables.push(this.rgwUserService.getQuota(uid));
182 observableForkJoin(observables).subscribe(
183 (resp: any[]) => {
11fdf7f2
TL
184 // Get the default values.
185 const defaults = _.clone(this.userForm.value);
186 // Extract the values displayed in the form.
187 let value = _.pick(resp[0], _.keys(this.userForm.value));
9f95a23c
TL
188 // Map the max. buckets values.
189 switch (value['max_buckets']) {
190 case -1:
191 value['max_buckets_mode'] = -1;
192 value['max_buckets'] = '';
193 break;
194 case 0:
195 value['max_buckets_mode'] = 0;
196 value['max_buckets'] = '';
197 break;
198 default:
199 value['max_buckets_mode'] = 1;
200 break;
201 }
11fdf7f2
TL
202 // Map the quota values.
203 ['user', 'bucket'].forEach((type) => {
204 const quota = resp[1][type + '_quota'];
205 value[type + '_quota_enabled'] = quota.enabled;
206 if (quota.max_size < 0) {
207 value[type + '_quota_max_size_unlimited'] = true;
208 value[type + '_quota_max_size'] = null;
209 } else {
210 value[type + '_quota_max_size_unlimited'] = false;
211 value[type + '_quota_max_size'] = `${quota.max_size} B`;
212 }
213 if (quota.max_objects < 0) {
214 value[type + '_quota_max_objects_unlimited'] = true;
215 value[type + '_quota_max_objects'] = null;
216 } else {
217 value[type + '_quota_max_objects_unlimited'] = false;
218 value[type + '_quota_max_objects'] = quota.max_objects;
219 }
220 });
221 // Merge with default values.
222 value = _.merge(defaults, value);
223 // Update the form.
224 this.userForm.setValue(value);
225
226 // Get the sub users.
227 this.subusers = resp[0].subusers;
228
229 // Get the keys.
230 this.s3Keys = resp[0].keys;
231 this.swiftKeys = resp[0].swift_keys;
232
233 // Process the capabilities.
234 const mapPerm = { 'read, write': '*' };
9f95a23c 235 resp[0].caps.forEach((cap: any) => {
11fdf7f2
TL
236 if (cap.perm in mapPerm) {
237 cap.perm = mapPerm[cap.perm];
238 }
239 });
240 this.capabilities = resp[0].caps;
f67539c2
TL
241
242 this.loadingReady();
11fdf7f2 243 },
f67539c2
TL
244 () => {
245 this.loadingError();
11fdf7f2
TL
246 }
247 );
248 });
249 }
250
251 goToListView() {
252 this.router.navigate(['/rgw/user']);
253 }
254
255 onSubmit() {
256 let notificationTitle: string;
257 // Exit immediately if the form isn't dirty.
258 if (this.userForm.pristine) {
259 this.goToListView();
260 return;
261 }
f67539c2 262 const uid = this.getUID();
11fdf7f2
TL
263 if (this.editing) {
264 // Edit
265 if (this._isGeneralDirty()) {
266 const args = this._getUpdateArgs();
267 this.submitObservables.push(this.rgwUserService.update(uid, args));
268 }
f67539c2 269 notificationTitle = $localize`Updated Object Gateway user '${uid}'`;
11fdf7f2
TL
270 } else {
271 // Add
272 const args = this._getCreateArgs();
273 this.submitObservables.push(this.rgwUserService.create(args));
f67539c2 274 notificationTitle = $localize`Created Object Gateway user '${uid}'`;
11fdf7f2
TL
275 }
276 // Check if user quota has been modified.
277 if (this._isUserQuotaDirty()) {
278 const userQuotaArgs = this._getUserQuotaArgs();
279 this.submitObservables.push(this.rgwUserService.updateQuota(uid, userQuotaArgs));
280 }
281 // Check if bucket quota has been modified.
282 if (this._isBucketQuotaDirty()) {
283 const bucketQuotaArgs = this._getBucketQuotaArgs();
284 this.submitObservables.push(this.rgwUserService.updateQuota(uid, bucketQuotaArgs));
285 }
92f5a8d4
TL
286 // Finally execute all observables one by one in serial.
287 observableConcat(...this.submitObservables).subscribe({
288 error: () => {
11fdf7f2
TL
289 // Reset the 'Submit' button.
290 this.userForm.setErrors({ cdSubmitButton: true });
92f5a8d4
TL
291 },
292 complete: () => {
293 this.notificationService.show(NotificationType.success, notificationTitle);
294 this.goToListView();
11fdf7f2 295 }
92f5a8d4 296 });
11fdf7f2
TL
297 }
298
f67539c2
TL
299 updateFieldsWhenTenanted() {
300 this.showTenant = this.userForm.getValue('show_tenant');
301 if (!this.showTenant) {
302 this.userForm.get('user_id').markAsUntouched();
303 this.userForm.get('tenant').patchValue(this.previousTenant);
304 } else {
305 this.userForm.get('user_id').markAsTouched();
306 this.previousTenant = this.userForm.get('tenant').value;
307 this.userForm.get('tenant').patchValue(null);
308 }
309 }
310
311 getUID(): string {
312 let uid = this.userForm.getValue('user_id');
313 const tenant = this.userForm?.getValue('tenant');
314 if (tenant && tenant.length > 0) {
315 uid = `${this.userForm.getValue('tenant')}$${uid}`;
316 }
317 return uid;
318 }
319
11fdf7f2 320 /**
494da23a 321 * Validate the quota maximum size, e.g. 1096, 1K, 30M or 1.9MiB.
11fdf7f2
TL
322 */
323 quotaMaxSizeValidator(control: AbstractControl): ValidationErrors | null {
324 if (isEmptyInputValue(control.value)) {
325 return null;
326 }
494da23a 327 const m = RegExp('^(\\d+(\\.\\d+)?)\\s*(B|K(B|iB)?|M(B|iB)?|G(B|iB)?|T(B|iB)?)?$', 'i').exec(
11fdf7f2
TL
328 control.value
329 );
330 if (m === null) {
331 return { quotaMaxSize: true };
332 }
333 const bytes = new FormatterService().toBytes(control.value);
334 return bytes < 1024 ? { quotaMaxSize: true } : null;
335 }
336
337 /**
338 * Add/Update a subuser.
339 */
340 setSubuser(subuser: RgwUserSubuser, index?: number) {
9f95a23c 341 const mapPermissions: Record<string, string> = {
11fdf7f2
TL
342 'full-control': 'full',
343 'read-write': 'readwrite'
344 };
f67539c2 345 const uid = this.getUID();
11fdf7f2
TL
346 const args = {
347 subuser: subuser.id,
348 access:
349 subuser.permissions in mapPermissions
350 ? mapPermissions[subuser.permissions]
351 : subuser.permissions,
352 key_type: 'swift',
353 secret_key: subuser.secret_key,
354 generate_secret: subuser.generate_secret ? 'true' : 'false'
355 };
356 this.submitObservables.push(this.rgwUserService.createSubuser(uid, args));
357 if (_.isNumber(index)) {
358 // Modify
359 // Create an observable to modify the subuser when the form is submitted.
360 this.subusers[index] = subuser;
361 } else {
362 // Add
363 // Create an observable to add the subuser when the form is submitted.
364 this.subusers.push(subuser);
365 // Add a Swift key. If the secret key is auto-generated, then visualize
366 // this to the user by displaying a notification instead of the key.
367 this.swiftKeys.push({
368 user: subuser.id,
369 secret_key: subuser.generate_secret ? 'Apply your changes first...' : subuser.secret_key
370 });
371 }
372 // Mark the form as dirty to be able to submit it.
373 this.userForm.markAsDirty();
374 }
375
376 /**
377 * Delete a subuser.
378 * @param {number} index The subuser to delete.
379 */
380 deleteSubuser(index: number) {
381 const subuser = this.subusers[index];
382 // Create an observable to delete the subuser when the form is submitted.
f67539c2 383 this.submitObservables.push(this.rgwUserService.deleteSubuser(this.getUID(), subuser.id));
11fdf7f2
TL
384 // Remove the associated S3 keys.
385 this.s3Keys = this.s3Keys.filter((key) => {
386 return key.user !== subuser.id;
387 });
388 // Remove the associated Swift keys.
389 this.swiftKeys = this.swiftKeys.filter((key) => {
390 return key.user !== subuser.id;
391 });
392 // Remove the subuser to update the UI.
393 this.subusers.splice(index, 1);
394 // Mark the form as dirty to be able to submit it.
395 this.userForm.markAsDirty();
396 }
397
398 /**
399 * Add/Update a capability.
400 */
401 setCapability(cap: RgwUserCapability, index?: number) {
f67539c2 402 const uid = this.getUID();
11fdf7f2
TL
403 if (_.isNumber(index)) {
404 // Modify
405 const oldCap = this.capabilities[index];
406 // Note, the RadosGW Admin OPS API does not support the modification of
407 // user capabilities. Because of that it is necessary to delete it and
408 // then to re-add the capability with its new value/permission.
409 this.submitObservables.push(
410 this.rgwUserService.deleteCapability(uid, oldCap.type, oldCap.perm)
411 );
412 this.submitObservables.push(this.rgwUserService.addCapability(uid, cap.type, cap.perm));
413 this.capabilities[index] = cap;
414 } else {
415 // Add
416 // Create an observable to add the capability when the form is submitted.
417 this.submitObservables.push(this.rgwUserService.addCapability(uid, cap.type, cap.perm));
f67539c2 418 this.capabilities = [...this.capabilities, cap]; // Notify Angular CD
11fdf7f2
TL
419 }
420 // Mark the form as dirty to be able to submit it.
421 this.userForm.markAsDirty();
422 }
423
424 /**
425 * Delete the given capability:
426 * - Delete it from the local array to update the UI
427 * - Create an observable that will be executed on form submit
428 * @param {number} index The capability to delete.
429 */
430 deleteCapability(index: number) {
431 const cap = this.capabilities[index];
432 // Create an observable to delete the capability when the form is submitted.
433 this.submitObservables.push(
f67539c2 434 this.rgwUserService.deleteCapability(this.getUID(), cap.type, cap.perm)
11fdf7f2
TL
435 );
436 // Remove the capability to update the UI.
437 this.capabilities.splice(index, 1);
f67539c2 438 this.capabilities = [...this.capabilities]; // Notify Angular CD
11fdf7f2
TL
439 // Mark the form as dirty to be able to submit it.
440 this.userForm.markAsDirty();
441 }
442
f67539c2
TL
443 hasAllCapabilities(capabilities: RgwUserCapability[]) {
444 return !_.difference(RgwUserCapabilities.getAll(), _.map(capabilities, 'type')).length;
92f5a8d4
TL
445 }
446
11fdf7f2
TL
447 /**
448 * Add/Update a S3 key.
449 */
450 setS3Key(key: RgwUserS3Key, index?: number) {
451 if (_.isNumber(index)) {
452 // Modify
453 // Nothing to do here at the moment.
454 } else {
455 // Add
456 // Split the key's user name into its user and subuser parts.
457 const userMatches = key.user.match(/([^:]+)(:(.+))?/);
458 // Create an observable to add the S3 key when the form is submitted.
459 const uid = userMatches[1];
460 const args = {
461 subuser: userMatches[2] ? userMatches[3] : '',
92f5a8d4 462 generate_key: key.generate_key ? 'true' : 'false'
11fdf7f2 463 };
92f5a8d4
TL
464 if (args['generate_key'] === 'false') {
465 if (!_.isNil(key.access_key)) {
466 args['access_key'] = key.access_key;
467 }
468 if (!_.isNil(key.secret_key)) {
469 args['secret_key'] = key.secret_key;
470 }
471 }
11fdf7f2
TL
472 this.submitObservables.push(this.rgwUserService.addS3Key(uid, args));
473 // If the access and the secret key are auto-generated, then visualize
474 // this to the user by displaying a notification instead of the key.
475 this.s3Keys.push({
476 user: key.user,
477 access_key: key.generate_key ? 'Apply your changes first...' : key.access_key,
478 secret_key: key.generate_key ? 'Apply your changes first...' : key.secret_key
479 });
480 }
481 // Mark the form as dirty to be able to submit it.
482 this.userForm.markAsDirty();
483 }
484
485 /**
486 * Delete a S3 key.
487 * @param {number} index The S3 key to delete.
488 */
489 deleteS3Key(index: number) {
490 const key = this.s3Keys[index];
491 // Create an observable to delete the S3 key when the form is submitted.
f67539c2 492 this.submitObservables.push(this.rgwUserService.deleteS3Key(this.getUID(), key.access_key));
11fdf7f2
TL
493 // Remove the S3 key to update the UI.
494 this.s3Keys.splice(index, 1);
495 // Mark the form as dirty to be able to submit it.
496 this.userForm.markAsDirty();
497 }
498
499 /**
500 * Show the specified subuser in a modal dialog.
501 * @param {number | undefined} index The subuser to show.
502 */
503 showSubuserModal(index?: number) {
f67539c2
TL
504 const uid = this.getUID();
505 const modalRef = this.modalService.show(RgwUserSubuserModalComponent);
11fdf7f2
TL
506 if (_.isNumber(index)) {
507 // Edit
508 const subuser = this.subusers[index];
f67539c2
TL
509 modalRef.componentInstance.setEditing();
510 modalRef.componentInstance.setValues(uid, subuser.id, subuser.permissions);
11fdf7f2
TL
511 } else {
512 // Add
f67539c2
TL
513 modalRef.componentInstance.setEditing(false);
514 modalRef.componentInstance.setValues(uid);
515 modalRef.componentInstance.setSubusers(this.subusers);
11fdf7f2 516 }
f67539c2 517 modalRef.componentInstance.submitAction.subscribe((subuser: RgwUserSubuser) => {
11fdf7f2
TL
518 this.setSubuser(subuser, index);
519 });
520 }
521
522 /**
523 * Show the specified S3 key in a modal dialog.
524 * @param {number | undefined} index The S3 key to show.
525 */
526 showS3KeyModal(index?: number) {
f67539c2 527 const modalRef = this.modalService.show(RgwUserS3KeyModalComponent);
11fdf7f2
TL
528 if (_.isNumber(index)) {
529 // View
530 const key = this.s3Keys[index];
f67539c2
TL
531 modalRef.componentInstance.setViewing();
532 modalRef.componentInstance.setValues(key.user, key.access_key, key.secret_key);
11fdf7f2
TL
533 } else {
534 // Add
535 const candidates = this._getS3KeyUserCandidates();
f67539c2
TL
536 modalRef.componentInstance.setViewing(false);
537 modalRef.componentInstance.setUserCandidates(candidates);
538 modalRef.componentInstance.submitAction.subscribe((key: RgwUserS3Key) => {
11fdf7f2
TL
539 this.setS3Key(key);
540 });
541 }
542 }
543
544 /**
545 * Show the specified Swift key in a modal dialog.
546 * @param {number} index The Swift key to show.
547 */
548 showSwiftKeyModal(index: number) {
f67539c2 549 const modalRef = this.modalService.show(RgwUserSwiftKeyModalComponent);
11fdf7f2 550 const key = this.swiftKeys[index];
f67539c2 551 modalRef.componentInstance.setValues(key.user, key.secret_key);
11fdf7f2
TL
552 }
553
554 /**
555 * Show the specified capability in a modal dialog.
556 * @param {number | undefined} index The S3 key to show.
557 */
558 showCapabilityModal(index?: number) {
f67539c2 559 const modalRef = this.modalService.show(RgwUserCapabilityModalComponent);
11fdf7f2
TL
560 if (_.isNumber(index)) {
561 // Edit
562 const cap = this.capabilities[index];
f67539c2
TL
563 modalRef.componentInstance.setEditing();
564 modalRef.componentInstance.setValues(cap.type, cap.perm);
11fdf7f2
TL
565 } else {
566 // Add
f67539c2
TL
567 modalRef.componentInstance.setEditing(false);
568 modalRef.componentInstance.setCapabilities(this.capabilities);
11fdf7f2 569 }
f67539c2 570 modalRef.componentInstance.submitAction.subscribe((cap: RgwUserCapability) => {
11fdf7f2
TL
571 this.setCapability(cap, index);
572 });
573 }
574
575 /**
576 * Check if the general user settings (display name, email, ...) have been modified.
577 * @return {Boolean} Returns TRUE if the general user settings have been modified.
578 */
579 private _isGeneralDirty(): boolean {
9f95a23c
TL
580 return ['display_name', 'email', 'max_buckets_mode', 'max_buckets', 'suspended'].some(
581 (path) => {
582 return this.userForm.get(path).dirty;
583 }
584 );
11fdf7f2
TL
585 }
586
587 /**
588 * Check if the user quota has been modified.
589 * @return {Boolean} Returns TRUE if the user quota has been modified.
590 */
591 private _isUserQuotaDirty(): boolean {
592 return [
593 'user_quota_enabled',
594 'user_quota_max_size_unlimited',
595 'user_quota_max_size',
596 'user_quota_max_objects_unlimited',
597 'user_quota_max_objects'
598 ].some((path) => {
599 return this.userForm.get(path).dirty;
600 });
601 }
602
603 /**
604 * Check if the bucket quota has been modified.
605 * @return {Boolean} Returns TRUE if the bucket quota has been modified.
606 */
607 private _isBucketQuotaDirty(): boolean {
608 return [
609 'bucket_quota_enabled',
610 'bucket_quota_max_size_unlimited',
611 'bucket_quota_max_size',
612 'bucket_quota_max_objects_unlimited',
613 'bucket_quota_max_objects'
614 ].some((path) => {
615 return this.userForm.get(path).dirty;
616 });
617 }
618
619 /**
620 * Helper function to get the arguments of the API request when a new
621 * user is created.
622 */
623 private _getCreateArgs() {
624 const result = {
f67539c2 625 uid: this.getUID(),
11fdf7f2
TL
626 display_name: this.userForm.getValue('display_name'),
627 suspended: this.userForm.getValue('suspended'),
628 email: '',
629 max_buckets: this.userForm.getValue('max_buckets'),
630 generate_key: this.userForm.getValue('generate_key'),
631 access_key: '',
632 secret_key: ''
633 };
634 const email = this.userForm.getValue('email');
635 if (_.isString(email) && email.length > 0) {
636 _.merge(result, { email: email });
637 }
638 const generateKey = this.userForm.getValue('generate_key');
639 if (!generateKey) {
640 _.merge(result, {
641 generate_key: false,
642 access_key: this.userForm.getValue('access_key'),
643 secret_key: this.userForm.getValue('secret_key')
644 });
645 }
9f95a23c
TL
646 const maxBucketsMode = parseInt(this.userForm.getValue('max_buckets_mode'), 10);
647 if (_.includes([-1, 0], maxBucketsMode)) {
648 // -1 => Disable bucket creation.
649 // 0 => Unlimited bucket creation.
650 _.merge(result, { max_buckets: maxBucketsMode });
651 }
11fdf7f2
TL
652 return result;
653 }
654
655 /**
656 * Helper function to get the arguments for the API request when the user
657 * configuration has been modified.
658 */
659 private _getUpdateArgs() {
9f95a23c 660 const result: Record<string, any> = {};
11fdf7f2
TL
661 const keys = ['display_name', 'email', 'max_buckets', 'suspended'];
662 for (const key of keys) {
663 result[key] = this.userForm.getValue(key);
664 }
9f95a23c
TL
665 const maxBucketsMode = parseInt(this.userForm.getValue('max_buckets_mode'), 10);
666 if (_.includes([-1, 0], maxBucketsMode)) {
667 // -1 => Disable bucket creation.
668 // 0 => Unlimited bucket creation.
669 result['max_buckets'] = maxBucketsMode;
670 }
11fdf7f2
TL
671 return result;
672 }
673
674 /**
675 * Helper function to get the arguments for the API request when the user
676 * quota configuration has been modified.
677 */
9f95a23c 678 private _getUserQuotaArgs(): Record<string, any> {
11fdf7f2
TL
679 const result = {
680 quota_type: 'user',
681 enabled: this.userForm.getValue('user_quota_enabled'),
682 max_size_kb: -1,
683 max_objects: -1
684 };
685 if (!this.userForm.getValue('user_quota_max_size_unlimited')) {
686 // Convert the given value to bytes.
687 const bytes = new FormatterService().toBytes(this.userForm.getValue('user_quota_max_size'));
688 // Finally convert the value to KiB.
689 result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
690 }
691 if (!this.userForm.getValue('user_quota_max_objects_unlimited')) {
692 result['max_objects'] = this.userForm.getValue('user_quota_max_objects');
693 }
694 return result;
695 }
696
697 /**
698 * Helper function to get the arguments for the API request when the bucket
699 * quota configuration has been modified.
700 */
9f95a23c 701 private _getBucketQuotaArgs(): Record<string, any> {
11fdf7f2
TL
702 const result = {
703 quota_type: 'bucket',
704 enabled: this.userForm.getValue('bucket_quota_enabled'),
705 max_size_kb: -1,
706 max_objects: -1
707 };
708 if (!this.userForm.getValue('bucket_quota_max_size_unlimited')) {
709 // Convert the given value to bytes.
710 const bytes = new FormatterService().toBytes(this.userForm.getValue('bucket_quota_max_size'));
711 // Finally convert the value to KiB.
712 result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
713 }
714 if (!this.userForm.getValue('bucket_quota_max_objects_unlimited')) {
715 result['max_objects'] = this.userForm.getValue('bucket_quota_max_objects');
716 }
717 return result;
718 }
719
720 /**
721 * Helper method to get the user candidates for S3 keys.
722 * @returns {Array} Returns a list of user identifiers.
723 */
724 private _getS3KeyUserCandidates() {
725 let result = [];
726 // Add the current user id.
f67539c2 727 const uid = this.getUID();
11fdf7f2
TL
728 if (_.isString(uid) && !_.isEmpty(uid)) {
729 result.push(uid);
730 }
731 // Append the subusers.
732 this.subusers.forEach((subUser) => {
733 result.push(subUser.id);
734 });
735 // Note that it's possible to create multiple S3 key pairs for a user,
736 // thus we append already configured users, too.
737 this.s3Keys.forEach((key) => {
738 result.push(key.user);
739 });
740 result = _.uniq(result);
741 return result;
742 }
f6b5b4d7
TL
743
744 onMaxBucketsModeChange(mode: string) {
745 if (mode === '1') {
746 // If 'Custom' mode is selected, then ensure that the form field
747 // 'Max. buckets' contains a valid value. Set it to default if
748 // necessary.
749 if (!this.userForm.get('max_buckets').valid) {
750 this.userForm.patchValue({
751 max_buckets: 1000
752 });
753 }
754 }
755 }
11fdf7f2 756}