summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts756
1 files changed, 756 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
new file mode 100644
index 000000000..5ab333771
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
@@ -0,0 +1,756 @@
+import { Component, OnInit } from '@angular/core';
+import { AbstractControl, ValidationErrors, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+
+import _ from 'lodash';
+import { concat as observableConcat, forkJoin as observableForkJoin, Observable } from 'rxjs';
+
+import { RgwUserService } from '~/app/shared/api/rgw-user.service';
+import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { CdForm } from '~/app/shared/forms/cd-form';
+import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdValidators, isEmptyInputValue } from '~/app/shared/forms/cd-validators';
+import { FormatterService } from '~/app/shared/services/formatter.service';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { RgwUserCapabilities } from '../models/rgw-user-capabilities';
+import { RgwUserCapability } from '../models/rgw-user-capability';
+import { RgwUserS3Key } from '../models/rgw-user-s3-key';
+import { RgwUserSubuser } from '../models/rgw-user-subuser';
+import { RgwUserSwiftKey } from '../models/rgw-user-swift-key';
+import { RgwUserCapabilityModalComponent } from '../rgw-user-capability-modal/rgw-user-capability-modal.component';
+import { RgwUserS3KeyModalComponent } from '../rgw-user-s3-key-modal/rgw-user-s3-key-modal.component';
+import { RgwUserSubuserModalComponent } from '../rgw-user-subuser-modal/rgw-user-subuser-modal.component';
+import { RgwUserSwiftKeyModalComponent } from '../rgw-user-swift-key-modal/rgw-user-swift-key-modal.component';
+
+@Component({
+ selector: 'cd-rgw-user-form',
+ templateUrl: './rgw-user-form.component.html',
+ styleUrls: ['./rgw-user-form.component.scss']
+})
+export class RgwUserFormComponent extends CdForm implements OnInit {
+ userForm: CdFormGroup;
+ editing = false;
+ submitObservables: Observable<Object>[] = [];
+ icons = Icons;
+ subusers: RgwUserSubuser[] = [];
+ s3Keys: RgwUserS3Key[] = [];
+ swiftKeys: RgwUserSwiftKey[] = [];
+ capabilities: RgwUserCapability[] = [];
+
+ action: string;
+ resource: string;
+ subuserLabel: string;
+ s3keyLabel: string;
+ capabilityLabel: string;
+ usernameExists: boolean;
+ showTenant = false;
+ previousTenant: string = null;
+
+ constructor(
+ private formBuilder: CdFormBuilder,
+ private route: ActivatedRoute,
+ private router: Router,
+ private rgwUserService: RgwUserService,
+ private modalService: ModalService,
+ private notificationService: NotificationService,
+ public actionLabels: ActionLabelsI18n
+ ) {
+ super();
+ this.resource = $localize`user`;
+ this.subuserLabel = $localize`subuser`;
+ this.s3keyLabel = $localize`S3 Key`;
+ this.capabilityLabel = $localize`capability`;
+ this.editing = this.router.url.startsWith(`/rgw/user/${URLVerbs.EDIT}`);
+ this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
+ this.createForm();
+ }
+
+ createForm() {
+ this.userForm = this.formBuilder.group({
+ // General
+ user_id: [
+ null,
+ [Validators.required, Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)],
+ this.editing
+ ? []
+ : [
+ CdValidators.unique(this.rgwUserService.exists, this.rgwUserService, () =>
+ this.userForm.getValue('tenant')
+ )
+ ]
+ ],
+ show_tenant: [this.editing],
+ tenant: [
+ null,
+ [Validators.pattern(/^[a-zA-Z0-9!@#%^&*()_-]+$/)],
+ this.editing
+ ? []
+ : [
+ CdValidators.unique(
+ this.rgwUserService.exists,
+ this.rgwUserService,
+ () => this.userForm.getValue('user_id'),
+ true
+ )
+ ]
+ ],
+ display_name: [null, [Validators.required]],
+ email: [
+ null,
+ [CdValidators.email],
+ [CdValidators.unique(this.rgwUserService.emailExists, this.rgwUserService)]
+ ],
+ max_buckets_mode: [1],
+ max_buckets: [
+ 1000,
+ [CdValidators.requiredIf({ max_buckets_mode: '1' }), CdValidators.number(false)]
+ ],
+ suspended: [false],
+ // S3 key
+ generate_key: [true],
+ access_key: [null, [CdValidators.requiredIf({ generate_key: false })]],
+ secret_key: [null, [CdValidators.requiredIf({ generate_key: false })]],
+ // User quota
+ user_quota_enabled: [false],
+ user_quota_max_size_unlimited: [true],
+ user_quota_max_size: [
+ null,
+ [
+ CdValidators.composeIf(
+ {
+ user_quota_enabled: true,
+ user_quota_max_size_unlimited: false
+ },
+ [Validators.required, this.quotaMaxSizeValidator]
+ )
+ ]
+ ],
+ user_quota_max_objects_unlimited: [true],
+ user_quota_max_objects: [
+ null,
+ [
+ CdValidators.requiredIf({
+ user_quota_enabled: true,
+ user_quota_max_objects_unlimited: false
+ })
+ ]
+ ],
+ // Bucket quota
+ bucket_quota_enabled: [false],
+ bucket_quota_max_size_unlimited: [true],
+ bucket_quota_max_size: [
+ null,
+ [
+ CdValidators.composeIf(
+ {
+ bucket_quota_enabled: true,
+ bucket_quota_max_size_unlimited: false
+ },
+ [Validators.required, this.quotaMaxSizeValidator]
+ )
+ ]
+ ],
+ bucket_quota_max_objects_unlimited: [true],
+ bucket_quota_max_objects: [
+ null,
+ [
+ CdValidators.requiredIf({
+ bucket_quota_enabled: true,
+ bucket_quota_max_objects_unlimited: false
+ })
+ ]
+ ]
+ });
+ }
+
+ ngOnInit() {
+ // Process route parameters.
+ this.route.params.subscribe((params: { uid: string }) => {
+ if (!params.hasOwnProperty('uid')) {
+ this.loadingReady();
+ return;
+ }
+ const uid = decodeURIComponent(params.uid);
+ // Load the user and quota information.
+ const observables = [];
+ observables.push(this.rgwUserService.get(uid));
+ observables.push(this.rgwUserService.getQuota(uid));
+ observableForkJoin(observables).subscribe(
+ (resp: any[]) => {
+ // Get the default values.
+ const defaults = _.clone(this.userForm.value);
+ // Extract the values displayed in the form.
+ let value = _.pick(resp[0], _.keys(this.userForm.value));
+ // Map the max. buckets values.
+ switch (value['max_buckets']) {
+ case -1:
+ value['max_buckets_mode'] = -1;
+ value['max_buckets'] = '';
+ break;
+ case 0:
+ value['max_buckets_mode'] = 0;
+ value['max_buckets'] = '';
+ break;
+ default:
+ value['max_buckets_mode'] = 1;
+ break;
+ }
+ // Map the quota values.
+ ['user', 'bucket'].forEach((type) => {
+ const quota = resp[1][type + '_quota'];
+ value[type + '_quota_enabled'] = quota.enabled;
+ if (quota.max_size < 0) {
+ value[type + '_quota_max_size_unlimited'] = true;
+ value[type + '_quota_max_size'] = null;
+ } else {
+ value[type + '_quota_max_size_unlimited'] = false;
+ value[type + '_quota_max_size'] = `${quota.max_size} B`;
+ }
+ if (quota.max_objects < 0) {
+ value[type + '_quota_max_objects_unlimited'] = true;
+ value[type + '_quota_max_objects'] = null;
+ } else {
+ value[type + '_quota_max_objects_unlimited'] = false;
+ value[type + '_quota_max_objects'] = quota.max_objects;
+ }
+ });
+ // Merge with default values.
+ value = _.merge(defaults, value);
+ // Update the form.
+ this.userForm.setValue(value);
+
+ // Get the sub users.
+ this.subusers = resp[0].subusers;
+
+ // Get the keys.
+ this.s3Keys = resp[0].keys;
+ this.swiftKeys = resp[0].swift_keys;
+
+ // Process the capabilities.
+ const mapPerm = { 'read, write': '*' };
+ resp[0].caps.forEach((cap: any) => {
+ if (cap.perm in mapPerm) {
+ cap.perm = mapPerm[cap.perm];
+ }
+ });
+ this.capabilities = resp[0].caps;
+
+ this.loadingReady();
+ },
+ () => {
+ this.loadingError();
+ }
+ );
+ });
+ }
+
+ goToListView() {
+ this.router.navigate(['/rgw/user']);
+ }
+
+ onSubmit() {
+ let notificationTitle: string;
+ // Exit immediately if the form isn't dirty.
+ if (this.userForm.pristine) {
+ this.goToListView();
+ return;
+ }
+ const uid = this.getUID();
+ if (this.editing) {
+ // Edit
+ if (this._isGeneralDirty()) {
+ const args = this._getUpdateArgs();
+ this.submitObservables.push(this.rgwUserService.update(uid, args));
+ }
+ notificationTitle = $localize`Updated Object Gateway user '${uid}'`;
+ } else {
+ // Add
+ const args = this._getCreateArgs();
+ this.submitObservables.push(this.rgwUserService.create(args));
+ notificationTitle = $localize`Created Object Gateway user '${uid}'`;
+ }
+ // Check if user quota has been modified.
+ if (this._isUserQuotaDirty()) {
+ const userQuotaArgs = this._getUserQuotaArgs();
+ this.submitObservables.push(this.rgwUserService.updateQuota(uid, userQuotaArgs));
+ }
+ // Check if bucket quota has been modified.
+ if (this._isBucketQuotaDirty()) {
+ const bucketQuotaArgs = this._getBucketQuotaArgs();
+ this.submitObservables.push(this.rgwUserService.updateQuota(uid, bucketQuotaArgs));
+ }
+ // Finally execute all observables one by one in serial.
+ observableConcat(...this.submitObservables).subscribe({
+ error: () => {
+ // Reset the 'Submit' button.
+ this.userForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => {
+ this.notificationService.show(NotificationType.success, notificationTitle);
+ this.goToListView();
+ }
+ });
+ }
+
+ updateFieldsWhenTenanted() {
+ this.showTenant = this.userForm.getValue('show_tenant');
+ if (!this.showTenant) {
+ this.userForm.get('user_id').markAsUntouched();
+ this.userForm.get('tenant').patchValue(this.previousTenant);
+ } else {
+ this.userForm.get('user_id').markAsTouched();
+ this.previousTenant = this.userForm.get('tenant').value;
+ this.userForm.get('tenant').patchValue(null);
+ }
+ }
+
+ getUID(): string {
+ let uid = this.userForm.getValue('user_id');
+ const tenant = this.userForm?.getValue('tenant');
+ if (tenant && tenant.length > 0) {
+ uid = `${this.userForm.getValue('tenant')}$${uid}`;
+ }
+ return uid;
+ }
+
+ /**
+ * Validate the quota maximum size, e.g. 1096, 1K, 30M or 1.9MiB.
+ */
+ quotaMaxSizeValidator(control: AbstractControl): ValidationErrors | null {
+ if (isEmptyInputValue(control.value)) {
+ return null;
+ }
+ const m = RegExp('^(\\d+(\\.\\d+)?)\\s*(B|K(B|iB)?|M(B|iB)?|G(B|iB)?|T(B|iB)?)?$', 'i').exec(
+ control.value
+ );
+ if (m === null) {
+ return { quotaMaxSize: true };
+ }
+ const bytes = new FormatterService().toBytes(control.value);
+ return bytes < 1024 ? { quotaMaxSize: true } : null;
+ }
+
+ /**
+ * Add/Update a subuser.
+ */
+ setSubuser(subuser: RgwUserSubuser, index?: number) {
+ const mapPermissions: Record<string, string> = {
+ 'full-control': 'full',
+ 'read-write': 'readwrite'
+ };
+ const uid = this.getUID();
+ const args = {
+ subuser: subuser.id,
+ access:
+ subuser.permissions in mapPermissions
+ ? mapPermissions[subuser.permissions]
+ : subuser.permissions,
+ key_type: 'swift',
+ secret_key: subuser.secret_key,
+ generate_secret: subuser.generate_secret ? 'true' : 'false'
+ };
+ this.submitObservables.push(this.rgwUserService.createSubuser(uid, args));
+ if (_.isNumber(index)) {
+ // Modify
+ // Create an observable to modify the subuser when the form is submitted.
+ this.subusers[index] = subuser;
+ } else {
+ // Add
+ // Create an observable to add the subuser when the form is submitted.
+ this.subusers.push(subuser);
+ // Add a Swift key. If the secret key is auto-generated, then visualize
+ // this to the user by displaying a notification instead of the key.
+ this.swiftKeys.push({
+ user: subuser.id,
+ secret_key: subuser.generate_secret ? 'Apply your changes first...' : subuser.secret_key
+ });
+ }
+ // Mark the form as dirty to be able to submit it.
+ this.userForm.markAsDirty();
+ }
+
+ /**
+ * Delete a subuser.
+ * @param {number} index The subuser to delete.
+ */
+ deleteSubuser(index: number) {
+ const subuser = this.subusers[index];
+ // Create an observable to delete the subuser when the form is submitted.
+ this.submitObservables.push(this.rgwUserService.deleteSubuser(this.getUID(), subuser.id));
+ // Remove the associated S3 keys.
+ this.s3Keys = this.s3Keys.filter((key) => {
+ return key.user !== subuser.id;
+ });
+ // Remove the associated Swift keys.
+ this.swiftKeys = this.swiftKeys.filter((key) => {
+ return key.user !== subuser.id;
+ });
+ // Remove the subuser to update the UI.
+ this.subusers.splice(index, 1);
+ // Mark the form as dirty to be able to submit it.
+ this.userForm.markAsDirty();
+ }
+
+ /**
+ * Add/Update a capability.
+ */
+ setCapability(cap: RgwUserCapability, index?: number) {
+ const uid = this.getUID();
+ if (_.isNumber(index)) {
+ // Modify
+ const oldCap = this.capabilities[index];
+ // Note, the RadosGW Admin OPS API does not support the modification of
+ // user capabilities. Because of that it is necessary to delete it and
+ // then to re-add the capability with its new value/permission.
+ this.submitObservables.push(
+ this.rgwUserService.deleteCapability(uid, oldCap.type, oldCap.perm)
+ );
+ this.submitObservables.push(this.rgwUserService.addCapability(uid, cap.type, cap.perm));
+ this.capabilities[index] = cap;
+ } else {
+ // Add
+ // Create an observable to add the capability when the form is submitted.
+ this.submitObservables.push(this.rgwUserService.addCapability(uid, cap.type, cap.perm));
+ this.capabilities = [...this.capabilities, cap]; // Notify Angular CD
+ }
+ // Mark the form as dirty to be able to submit it.
+ this.userForm.markAsDirty();
+ }
+
+ /**
+ * Delete the given capability:
+ * - Delete it from the local array to update the UI
+ * - Create an observable that will be executed on form submit
+ * @param {number} index The capability to delete.
+ */
+ deleteCapability(index: number) {
+ const cap = this.capabilities[index];
+ // Create an observable to delete the capability when the form is submitted.
+ this.submitObservables.push(
+ this.rgwUserService.deleteCapability(this.getUID(), cap.type, cap.perm)
+ );
+ // Remove the capability to update the UI.
+ this.capabilities.splice(index, 1);
+ this.capabilities = [...this.capabilities]; // Notify Angular CD
+ // Mark the form as dirty to be able to submit it.
+ this.userForm.markAsDirty();
+ }
+
+ hasAllCapabilities(capabilities: RgwUserCapability[]) {
+ return !_.difference(RgwUserCapabilities.getAll(), _.map(capabilities, 'type')).length;
+ }
+
+ /**
+ * Add/Update a S3 key.
+ */
+ setS3Key(key: RgwUserS3Key, index?: number) {
+ if (_.isNumber(index)) {
+ // Modify
+ // Nothing to do here at the moment.
+ } else {
+ // Add
+ // Split the key's user name into its user and subuser parts.
+ const userMatches = key.user.match(/([^:]+)(:(.+))?/);
+ // Create an observable to add the S3 key when the form is submitted.
+ const uid = userMatches[1];
+ const args = {
+ subuser: userMatches[2] ? userMatches[3] : '',
+ generate_key: key.generate_key ? 'true' : 'false'
+ };
+ if (args['generate_key'] === 'false') {
+ if (!_.isNil(key.access_key)) {
+ args['access_key'] = key.access_key;
+ }
+ if (!_.isNil(key.secret_key)) {
+ args['secret_key'] = key.secret_key;
+ }
+ }
+ this.submitObservables.push(this.rgwUserService.addS3Key(uid, args));
+ // If the access and the secret key are auto-generated, then visualize
+ // this to the user by displaying a notification instead of the key.
+ this.s3Keys.push({
+ user: key.user,
+ access_key: key.generate_key ? 'Apply your changes first...' : key.access_key,
+ secret_key: key.generate_key ? 'Apply your changes first...' : key.secret_key
+ });
+ }
+ // Mark the form as dirty to be able to submit it.
+ this.userForm.markAsDirty();
+ }
+
+ /**
+ * Delete a S3 key.
+ * @param {number} index The S3 key to delete.
+ */
+ deleteS3Key(index: number) {
+ const key = this.s3Keys[index];
+ // Create an observable to delete the S3 key when the form is submitted.
+ this.submitObservables.push(this.rgwUserService.deleteS3Key(this.getUID(), key.access_key));
+ // Remove the S3 key to update the UI.
+ this.s3Keys.splice(index, 1);
+ // Mark the form as dirty to be able to submit it.
+ this.userForm.markAsDirty();
+ }
+
+ /**
+ * Show the specified subuser in a modal dialog.
+ * @param {number | undefined} index The subuser to show.
+ */
+ showSubuserModal(index?: number) {
+ const uid = this.getUID();
+ const modalRef = this.modalService.show(RgwUserSubuserModalComponent);
+ if (_.isNumber(index)) {
+ // Edit
+ const subuser = this.subusers[index];
+ modalRef.componentInstance.setEditing();
+ modalRef.componentInstance.setValues(uid, subuser.id, subuser.permissions);
+ } else {
+ // Add
+ modalRef.componentInstance.setEditing(false);
+ modalRef.componentInstance.setValues(uid);
+ modalRef.componentInstance.setSubusers(this.subusers);
+ }
+ modalRef.componentInstance.submitAction.subscribe((subuser: RgwUserSubuser) => {
+ this.setSubuser(subuser, index);
+ });
+ }
+
+ /**
+ * Show the specified S3 key in a modal dialog.
+ * @param {number | undefined} index The S3 key to show.
+ */
+ showS3KeyModal(index?: number) {
+ const modalRef = this.modalService.show(RgwUserS3KeyModalComponent);
+ if (_.isNumber(index)) {
+ // View
+ const key = this.s3Keys[index];
+ modalRef.componentInstance.setViewing();
+ modalRef.componentInstance.setValues(key.user, key.access_key, key.secret_key);
+ } else {
+ // Add
+ const candidates = this._getS3KeyUserCandidates();
+ modalRef.componentInstance.setViewing(false);
+ modalRef.componentInstance.setUserCandidates(candidates);
+ modalRef.componentInstance.submitAction.subscribe((key: RgwUserS3Key) => {
+ this.setS3Key(key);
+ });
+ }
+ }
+
+ /**
+ * Show the specified Swift key in a modal dialog.
+ * @param {number} index The Swift key to show.
+ */
+ showSwiftKeyModal(index: number) {
+ const modalRef = this.modalService.show(RgwUserSwiftKeyModalComponent);
+ const key = this.swiftKeys[index];
+ modalRef.componentInstance.setValues(key.user, key.secret_key);
+ }
+
+ /**
+ * Show the specified capability in a modal dialog.
+ * @param {number | undefined} index The S3 key to show.
+ */
+ showCapabilityModal(index?: number) {
+ const modalRef = this.modalService.show(RgwUserCapabilityModalComponent);
+ if (_.isNumber(index)) {
+ // Edit
+ const cap = this.capabilities[index];
+ modalRef.componentInstance.setEditing();
+ modalRef.componentInstance.setValues(cap.type, cap.perm);
+ } else {
+ // Add
+ modalRef.componentInstance.setEditing(false);
+ modalRef.componentInstance.setCapabilities(this.capabilities);
+ }
+ modalRef.componentInstance.submitAction.subscribe((cap: RgwUserCapability) => {
+ this.setCapability(cap, index);
+ });
+ }
+
+ /**
+ * Check if the general user settings (display name, email, ...) have been modified.
+ * @return {Boolean} Returns TRUE if the general user settings have been modified.
+ */
+ private _isGeneralDirty(): boolean {
+ return ['display_name', 'email', 'max_buckets_mode', 'max_buckets', 'suspended'].some(
+ (path) => {
+ return this.userForm.get(path).dirty;
+ }
+ );
+ }
+
+ /**
+ * Check if the user quota has been modified.
+ * @return {Boolean} Returns TRUE if the user quota has been modified.
+ */
+ private _isUserQuotaDirty(): boolean {
+ return [
+ 'user_quota_enabled',
+ 'user_quota_max_size_unlimited',
+ 'user_quota_max_size',
+ 'user_quota_max_objects_unlimited',
+ 'user_quota_max_objects'
+ ].some((path) => {
+ return this.userForm.get(path).dirty;
+ });
+ }
+
+ /**
+ * Check if the bucket quota has been modified.
+ * @return {Boolean} Returns TRUE if the bucket quota has been modified.
+ */
+ private _isBucketQuotaDirty(): boolean {
+ return [
+ 'bucket_quota_enabled',
+ 'bucket_quota_max_size_unlimited',
+ 'bucket_quota_max_size',
+ 'bucket_quota_max_objects_unlimited',
+ 'bucket_quota_max_objects'
+ ].some((path) => {
+ return this.userForm.get(path).dirty;
+ });
+ }
+
+ /**
+ * Helper function to get the arguments of the API request when a new
+ * user is created.
+ */
+ private _getCreateArgs() {
+ const result = {
+ uid: this.getUID(),
+ display_name: this.userForm.getValue('display_name'),
+ suspended: this.userForm.getValue('suspended'),
+ email: '',
+ max_buckets: this.userForm.getValue('max_buckets'),
+ generate_key: this.userForm.getValue('generate_key'),
+ access_key: '',
+ secret_key: ''
+ };
+ const email = this.userForm.getValue('email');
+ if (_.isString(email) && email.length > 0) {
+ _.merge(result, { email: email });
+ }
+ const generateKey = this.userForm.getValue('generate_key');
+ if (!generateKey) {
+ _.merge(result, {
+ generate_key: false,
+ access_key: this.userForm.getValue('access_key'),
+ secret_key: this.userForm.getValue('secret_key')
+ });
+ }
+ const maxBucketsMode = parseInt(this.userForm.getValue('max_buckets_mode'), 10);
+ if (_.includes([-1, 0], maxBucketsMode)) {
+ // -1 => Disable bucket creation.
+ // 0 => Unlimited bucket creation.
+ _.merge(result, { max_buckets: maxBucketsMode });
+ }
+ return result;
+ }
+
+ /**
+ * Helper function to get the arguments for the API request when the user
+ * configuration has been modified.
+ */
+ private _getUpdateArgs() {
+ const result: Record<string, any> = {};
+ const keys = ['display_name', 'email', 'max_buckets', 'suspended'];
+ for (const key of keys) {
+ result[key] = this.userForm.getValue(key);
+ }
+ const maxBucketsMode = parseInt(this.userForm.getValue('max_buckets_mode'), 10);
+ if (_.includes([-1, 0], maxBucketsMode)) {
+ // -1 => Disable bucket creation.
+ // 0 => Unlimited bucket creation.
+ result['max_buckets'] = maxBucketsMode;
+ }
+ return result;
+ }
+
+ /**
+ * Helper function to get the arguments for the API request when the user
+ * quota configuration has been modified.
+ */
+ private _getUserQuotaArgs(): Record<string, any> {
+ const result = {
+ quota_type: 'user',
+ enabled: this.userForm.getValue('user_quota_enabled'),
+ max_size_kb: -1,
+ max_objects: -1
+ };
+ if (!this.userForm.getValue('user_quota_max_size_unlimited')) {
+ // Convert the given value to bytes.
+ const bytes = new FormatterService().toBytes(this.userForm.getValue('user_quota_max_size'));
+ // Finally convert the value to KiB.
+ result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
+ }
+ if (!this.userForm.getValue('user_quota_max_objects_unlimited')) {
+ result['max_objects'] = this.userForm.getValue('user_quota_max_objects');
+ }
+ return result;
+ }
+
+ /**
+ * Helper function to get the arguments for the API request when the bucket
+ * quota configuration has been modified.
+ */
+ private _getBucketQuotaArgs(): Record<string, any> {
+ const result = {
+ quota_type: 'bucket',
+ enabled: this.userForm.getValue('bucket_quota_enabled'),
+ max_size_kb: -1,
+ max_objects: -1
+ };
+ if (!this.userForm.getValue('bucket_quota_max_size_unlimited')) {
+ // Convert the given value to bytes.
+ const bytes = new FormatterService().toBytes(this.userForm.getValue('bucket_quota_max_size'));
+ // Finally convert the value to KiB.
+ result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
+ }
+ if (!this.userForm.getValue('bucket_quota_max_objects_unlimited')) {
+ result['max_objects'] = this.userForm.getValue('bucket_quota_max_objects');
+ }
+ return result;
+ }
+
+ /**
+ * Helper method to get the user candidates for S3 keys.
+ * @returns {Array} Returns a list of user identifiers.
+ */
+ private _getS3KeyUserCandidates() {
+ let result = [];
+ // Add the current user id.
+ const uid = this.getUID();
+ if (_.isString(uid) && !_.isEmpty(uid)) {
+ result.push(uid);
+ }
+ // Append the subusers.
+ this.subusers.forEach((subUser) => {
+ result.push(subUser.id);
+ });
+ // Note that it's possible to create multiple S3 key pairs for a user,
+ // thus we append already configured users, too.
+ this.s3Keys.forEach((key) => {
+ result.push(key.user);
+ });
+ result = _.uniq(result);
+ return result;
+ }
+
+ onMaxBucketsModeChange(mode: string) {
+ if (mode === '1') {
+ // If 'Custom' mode is selected, then ensure that the form field
+ // 'Max. buckets' contains a valid value. Set it to default if
+ // necessary.
+ if (!this.userForm.get('max_buckets').valid) {
+ this.userForm.patchValue({
+ max_buckets: 1000
+ });
+ }
+ }
+ }
+}