]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: account edit functionality added
authorNaman Munet <naman.munet@ibm.com>
Tue, 7 Jan 2025 19:20:32 +0000 (00:50 +0530)
committerNaman Munet <naman.munet@ibm.com>
Wed, 22 Jan 2025 11:35:33 +0000 (17:05 +0530)
Fixes: https://tracker.ceph.com/issues/69140
Signed-off-by: Naman Munet <naman.munet@ibm.com>
src/pybind/mgr/dashboard/controllers/rgw_iam.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rgw_iam.py

index 1a293a7a17af7a02b558d6320dc667d5553b9229..80d4b85862f672aa98ebcd97dbf125de2fdd4cf9 100644 (file)
@@ -12,11 +12,10 @@ class RgwUserAccountsController(RESTController):
 
     @allow_empty_body
     def create(self, account_name: Optional[str] = None, tenant: str = None,
-               account_id: Optional[str] = None, email: Optional[str] = None,
-               max_buckets: str = None, max_users: str = None,
-               max_roles: str = None, max_group: str = None,
+               email: Optional[str] = None, max_buckets: str = None,
+               max_users: str = None, max_roles: str = None, max_group: str = None,
                max_access_keys: str = None):
-        return RgwAccounts.create_account(account_name, tenant, account_id, email,
+        return RgwAccounts.create_account(account_name, tenant, email,
                                           max_buckets, max_users, max_roles,
                                           max_group, max_access_keys)
 
@@ -38,8 +37,13 @@ class RgwUserAccountsController(RESTController):
                  parameters={'account_id': (str, 'Account id')})
     @allow_empty_body
     def set(self, account_id: str, account_name: Optional[str] = None,
-            email: Optional[str] = None):
-        return RgwAccounts.modify_account(account_id, account_name, email)
+            email: Optional[str] = None, tenant: str = None,
+            max_buckets: str = None, max_users: str = None,
+            max_roles: str = None, max_group: str = None,
+            max_access_keys: str = None):
+        return RgwAccounts.modify_account(account_id, account_name, email, tenant,
+                                          max_buckets, max_users, max_roles,
+                                          max_group, max_access_keys)
 
     @EndpointDoc("Set RGW Account/Bucket quota",
                  parameters={'account_id': (str, 'Account id'),
index 279d36de94c41f246c7ae0856369e9cb80c67a86..b9ccddd0a56cdcea3be601521491842ce9921d97 100644 (file)
@@ -15,7 +15,7 @@
         <cds-text-label label="Account Name"
                         for="acc_name"
                         cdRequiredField="Account Name"
-                        [invalid]="!accountForm.controls.account_name.valid && accountForm.controls.account_name.dirty"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
                         [invalidText]="accountIdError"
                         i18n>Account Name
           <input cdsText
                  placeholder="Enter account name"
                  id="acc_name"
                  name="acc_name"
-                 formControlName="account_name"/>
+                 formControlName="name"/>
         </cds-text-label>
         <ng-template #accountIdError>
-          <span *ngIf="accountForm.showError('account_name', formDir, 'required')"
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
                 class="invalid-feedback">
             <ng-container i18n>This field is required.</ng-container>
           </span>
            cdsRow>
         <div cdsCol>
           <!-- Max. group mode -->
-          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_group_mode' }"></ng-container>
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_groups_mode' }"></ng-container>
         </div>
         <div cdsCol>
         <!-- Max group -->
-        <span *ngIf="1 == accountForm.get('max_group_mode').value">
-          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_group' }"></ng-container>
+        <span *ngIf="1 == accountForm.get('max_groups_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_groups' }"></ng-container>
           </span>
         </div>
       </div>
index 7d2734b61f63aacbf1ab825491bfc9999bc0f22a..8e0d81835667fbe5a4249f9ace1cca3b8f9effed 100644 (file)
@@ -1,6 +1,6 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { AbstractControl, ValidationErrors, Validators } from '@angular/forms';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 import { RgwUserAccountsService } from '~/app/shared/api/rgw-user-accounts.service';
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { CdForm } from '~/app/shared/forms/cd-form';
@@ -12,13 +12,14 @@ import { CdValidators, isEmptyInputValue } from '~/app/shared/forms/cd-validator
 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { FormatterService } from '~/app/shared/services/formatter.service';
 import { Observable, concat as observableConcat } from 'rxjs';
+import _ from 'lodash';
 
 @Component({
   selector: 'cd-rgw-user-accounts-form',
   templateUrl: './rgw-user-accounts-form.component.html',
   styleUrls: ['./rgw-user-accounts-form.component.scss']
 })
-export class RgwUserAccountsFormComponent extends CdForm {
+export class RgwUserAccountsFormComponent extends CdForm implements OnInit {
   accountForm: CdFormGroup;
   action: string;
   resource: string;
@@ -30,7 +31,8 @@ export class RgwUserAccountsFormComponent extends CdForm {
     private actionLabels: ActionLabelsI18n,
     private rgwUserAccountsService: RgwUserAccountsService,
     private notificationService: NotificationService,
-    private formBuilder: CdFormBuilder
+    private formBuilder: CdFormBuilder,
+    private route: ActivatedRoute
   ) {
     super();
     this.editing = this.router.url.includes('rgw/accounts/edit');
@@ -40,11 +42,76 @@ export class RgwUserAccountsFormComponent extends CdForm {
     this.loadingReady();
   }
 
+  ngOnInit(): void {
+    if (this.editing) {
+      this.route.paramMap.subscribe((params: any) => {
+        const account_id = params.get('id');
+        this.rgwUserAccountsService.get(account_id).subscribe((accountData: Account) => {
+          // Get the default values.
+          const defaults = _.clone(this.accountForm.value);
+          // Extract the values displayed in the form.
+          let value: any = _.pick(accountData, _.keys(this.accountForm.value));
+          // Map the max. values.
+          ['max_users', 'max_roles', 'max_groups', 'max_buckets', 'max_access_keys'].forEach(
+            (formControlName: string) => {
+              this.mapValuesForMode(value, formControlName);
+            }
+          );
+          // Map the quota values.
+          ['account', 'bucket'].forEach((type: string) => {
+            let quota: any = {};
+            if (type == 'bucket') {
+              quota = accountData.bucket_quota;
+            } else {
+              quota = accountData.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.accountForm.setValue(value);
+          this.accountForm.get('tenant').disable();
+        });
+      });
+    }
+  }
+
+  mapValuesForMode(value: any, formControlName: string) {
+    switch (value[formControlName]) {
+      case -1:
+        value[`${formControlName}_mode`] = -1;
+        value[formControlName] = '';
+        break;
+      case 0:
+        value[`${formControlName}_mode`] = 0;
+        value[formControlName] = '';
+        break;
+      default:
+        value[`${formControlName}_mode`] = 1;
+        break;
+    }
+  }
+
   private createForm() {
     this.accountForm = this.formBuilder.group({
-      account_id: [''],
+      id: [''],
       tenant: [''],
-      account_name: ['', Validators.required],
+      name: ['', Validators.required],
       email: ['', CdValidators.email],
       max_users_mode: [1],
       max_users: [
@@ -56,10 +123,10 @@ export class RgwUserAccountsFormComponent extends CdForm {
         1000,
         [CdValidators.requiredIf({ max_roles_mode: '1' }), CdValidators.number(false)]
       ],
-      max_group_mode: [1],
-      max_group: [
+      max_groups_mode: [1],
+      max_groups: [
         1000,
-        [CdValidators.requiredIf({ max_group_mode: '1' }), CdValidators.number(false)]
+        [CdValidators.requiredIf({ max_groups_mode: '1' }), CdValidators.number(false)]
       ],
       max_access_keys_mode: [1],
       max_access_keys: [
@@ -152,23 +219,37 @@ export class RgwUserAccountsFormComponent extends CdForm {
       return;
     }
 
+    const formvalue = this.accountForm.value;
+    const payload = {
+      account_id: formvalue.id,
+      account_name: formvalue.name,
+      email: formvalue.email,
+      tenant: formvalue.tenant,
+      max_users: this.getValueFromFormControl('max_users'),
+      max_buckets: this.getValueFromFormControl('max_buckets'),
+      max_roles: this.getValueFromFormControl('max_roles'),
+      max_group: this.getValueFromFormControl('max_groups'),
+      max_access_keys: this.getValueFromFormControl('max_access_keys')
+    };
     if (!this.editing) {
-      const formvalue = this.accountForm.value;
-      const createPayload = {
-        account_id: formvalue.account_id,
-        account_name: formvalue.account_name,
-        email: formvalue.email,
-        tenant: formvalue.tenant,
-        max_users: this.getValueFromFormControl('max_users'),
-        max_buckets: this.getValueFromFormControl('max_buckets'),
-        max_roles: this.getValueFromFormControl('max_roles'),
-        max_group: this.getValueFromFormControl('max_group'),
-        max_access_keys: this.getValueFromFormControl('max_access_keys')
-      };
+      delete payload.account_id;
       notificationTitle = $localize`Account created successfully`;
-      this.rgwUserAccountsService.create(createPayload).subscribe({
+      this.rgwUserAccountsService.create(payload).subscribe({
+        next: (account: Account) => {
+          this.accountForm.get('id').setValue(account.id);
+          this.setQuotaConfig();
+          this.notificationService.show(NotificationType.success, notificationTitle);
+        },
+        error: () => {
+          // Reset the 'Submit' button.
+          this.accountForm.setErrors({ cdSubmitButton: true });
+        }
+      });
+    } else {
+      notificationTitle = $localize`Account modified successfully`;
+      this.rgwUserAccountsService.modify(payload).subscribe({
         next: (account: Account) => {
-          this.accountForm.get('account_id').setValue(account.id);
+          this.accountForm.get('id').setValue(account.id);
           this.setQuotaConfig();
           this.notificationService.show(NotificationType.success, notificationTitle);
         },
@@ -181,7 +262,7 @@ export class RgwUserAccountsFormComponent extends CdForm {
   }
 
   setQuotaConfig() {
-    const accountId: string = this.accountForm.get('account_id').value;
+    const accountId: string = this.accountForm.get('id').value;
     // Check if account quota has been modified.
     if (this._isQuotaConfDirty('account')) {
       const accountQuotaArgs = this._getQuotaArgs('account');
@@ -239,18 +320,15 @@ export class RgwUserAccountsFormComponent extends CdForm {
    * @return {Boolean} Returns TRUE if the quota has been modified.
    */
   private _isQuotaConfDirty(quotaType: string): boolean {
-    if (this.accountForm.get(`${quotaType}_quota_enabled`).value) {
-      return [
-        `${quotaType}_quota_enabled`,
-        `${quotaType}_quota_max_size_unlimited`,
-        `${quotaType}_quota_max_size`,
-        `${quotaType}_quota_max_objects_unlimited`,
-        `${quotaType}_quota_max_objects`
-      ].some((path) => {
-        return this.accountForm.get(path).dirty;
-      });
-    }
-    return false;
+    return [
+      `${quotaType}_quota_enabled`,
+      `${quotaType}_quota_max_size_unlimited`,
+      `${quotaType}_quota_max_size`,
+      `${quotaType}_quota_max_objects_unlimited`,
+      `${quotaType}_quota_max_objects`
+    ].some((path) => {
+      return this.accountForm.get(path).dirty;
+    });
   }
 
   onModeChange(mode: string, formControlName: string) {
index 882a3f40cd2eed63d59b99b663aa9030550098ce..0ad449761c1785142bc29177877c880ae1d83a74 100644 (file)
@@ -90,6 +90,13 @@ export class RgwUserAccountsComponent extends ListWithDetails implements OnInit
         flexGrow: 1
       }
     ];
+    const getEditURL = () => {
+      if (this.selection.first().groupName && this.selection.first().id) {
+        return `${URLVerbs.EDIT}/${this.selection.first().id}
+        }`;
+      }
+      return `${URLVerbs.EDIT}/${this.selection.first().id}`;
+    };
     const addAction: CdTableAction = {
       permission: 'create',
       icon: Icons.add,
@@ -97,7 +104,13 @@ export class RgwUserAccountsComponent extends ListWithDetails implements OnInit
       name: this.actionLabels.CREATE,
       canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
     };
-    this.tableActions = [addAction];
+    const editAction: CdTableAction = {
+      permission: 'update',
+      icon: Icons.edit,
+      click: () => this.router.navigate([`${BASE_URL}/${getEditURL()}`]),
+      name: this.actionLabels.EDIT
+    };
+    this.tableActions = [addAction, editAction];
   }
 
   getAccountsList(context?: CdTableFetchDataContext) {
index 012e7c2fd21afe011d93d6367b15eb83eef1c205..f75c86607a9a9e3d1ff04ab90551e76f52739ad6 100644 (file)
@@ -203,6 +203,11 @@ const routes: Routes = [
         path: URLVerbs.CREATE,
         component: RgwUserAccountsFormComponent,
         data: { breadcrumbs: ActionLabels.CREATE }
+      },
+      {
+        path: `${URLVerbs.EDIT}/:id`,
+        component: RgwUserAccountsFormComponent,
+        data: { breadcrumbs: ActionLabels.EDIT }
       }
     ]
   },
index 971261013af97577e4e03670b108e795c47357c2..af53724d47d99cc42d2b3391bb148e235c31c24d 100644 (file)
@@ -30,6 +30,10 @@ export class RgwUserAccountsService {
     return this.http.post(this.url, payload);
   }
 
+  modify(payload: any): Observable<any> {
+    return this.http.put(`${this.url}/set`, payload);
+  }
+
   setQuota(
     account_id: string,
     payload: { quota_type: string; max_size: string; max_objects: string; enabled: boolean }
index c548ec32c0d7131d799d72435ac1be6618409226..d744eb6c42c0fc7f319380fa2d4d47ec4ea36ca4 100644 (file)
@@ -10842,8 +10842,6 @@ paths:
           application/json:
             schema:
               properties:
-                account_id:
-                  type: integer
                 account_name:
                   type: integer
                 email:
@@ -10964,6 +10962,18 @@ paths:
                   type: integer
                 email:
                   type: string
+                max_access_keys:
+                  type: string
+                max_buckets:
+                  type: string
+                max_group:
+                  type: string
+                max_roles:
+                  type: string
+                max_users:
+                  type: string
+                tenant:
+                  type: string
               type: object
       responses:
         '200':
@@ -11004,6 +11014,8 @@ paths:
           application/json:
             schema:
               properties:
+                enabled:
+                  type: string
                 max_objects:
                   type: string
                 max_size:
@@ -11015,6 +11027,7 @@ paths:
               - quota_type
               - max_size
               - max_objects
+              - enabled
               type: object
       responses:
         '200':
index 43a3f2be181db550af2dc4e9800c2e22496f94a7..896f1a932396a7b48a85111f1886cc942f47c940 100644 (file)
@@ -43,51 +43,30 @@ class RgwAccounts:
 
     @classmethod
     def create_account(cls, account_name: Optional[str] = None, tenant: str = None,
-                       account_id: Optional[str] = None, email: Optional[str] = None,
-                       max_buckets: str = None, max_users: str = None,
-                       max_roles: str = None, max_group: str = None,
-                       max_access_keys: str = None):
+                       email: Optional[str] = None, max_buckets: str = None,
+                       max_users: str = None, max_roles: str = None,
+                       max_group: str = None, max_access_keys: str = None):
         create_accounts_cmd = ['account', 'create']
 
-        if account_name:
-            create_accounts_cmd += ['--account-name', account_name]
-
-        if account_id:
-            create_accounts_cmd += ['--account_id', account_id]
-
-        if email:
-            create_accounts_cmd += ['--email', email]
-
-        if tenant:
-            create_accounts_cmd += ['--tenant', tenant]
-
-        if max_buckets:
-            create_accounts_cmd += ['--max_buckets', str(max_buckets)]
-
-        if max_users:
-            create_accounts_cmd += ['--max_users', str(max_users)]
-
-        if max_roles:
-            create_accounts_cmd += ['--max_roles', str(max_roles)]
-
-        if max_group:
-            create_accounts_cmd += ['--max_groups', str(max_group)]
-
-        if max_access_keys:
-            create_accounts_cmd += ['--max_access_keys', str(max_access_keys)]
+        create_accounts_cmd += cls.get_common_args_list(account_name, email,
+                                                        tenant, max_buckets,
+                                                        max_users, max_roles,
+                                                        max_group, max_access_keys)
 
         return cls.send_rgw_cmd(create_accounts_cmd)
 
     @classmethod
     def modify_account(cls, account_id: str, account_name: Optional[str] = None,
-                       email: Optional[str] = None):
+                       email: Optional[str] = None, tenant: str = None,
+                       max_buckets: str = None, max_users: str = None,
+                       max_roles: str = None, max_group: str = None,
+                       max_access_keys: str = None):
         modify_accounts_cmd = ['account', 'modify', '--account-id', account_id]
 
-        if account_name:
-            modify_accounts_cmd += ['--account-name', account_name]
-
-        if email:
-            modify_accounts_cmd += ['--email', email]
+        modify_accounts_cmd += cls.get_common_args_list(account_name, email,
+                                                        tenant, max_buckets,
+                                                        max_users, max_roles,
+                                                        max_group, max_access_keys)
 
         return cls.send_rgw_cmd(modify_accounts_cmd)
 
@@ -120,3 +99,36 @@ class RgwAccounts:
                                 '--account-id', account_id]
 
         return cls.send_rgw_cmd(set_quota_status_cmd)
+
+    @classmethod
+    def get_common_args_list(cls, account_name: Optional[str] = None,
+                             email: Optional[str] = None, tenant: str = None,
+                             max_buckets: str = None, max_users: str = None,
+                             max_roles: str = None, max_group: str = None,
+                             max_access_keys: str = None):
+        common_cmd_list = []
+        if account_name:
+            common_cmd_list += ['--account-name', account_name]
+
+        if email:
+            common_cmd_list += ['--email', email]
+
+        if tenant:
+            common_cmd_list += ['--tenant', tenant]
+
+        if max_buckets:
+            common_cmd_list += ['--max_buckets', str(max_buckets)]
+
+        if max_users:
+            common_cmd_list += ['--max_users', str(max_users)]
+
+        if max_roles:
+            common_cmd_list += ['--max_roles', str(max_roles)]
+
+        if max_group:
+            common_cmd_list += ['--max_groups', str(max_group)]
+
+        if max_access_keys:
+            common_cmd_list += ['--max_access_keys', str(max_access_keys)]
+
+        return common_cmd_list