From 47d74651abb3d2740a74e067c5aac219244259cf Mon Sep 17 00:00:00 2001 From: Naman Munet Date: Wed, 8 Jan 2025 00:50:32 +0530 Subject: [PATCH] mgr/dashboard: account edit functionality added Fixes: https://tracker.ceph.com/issues/69140 Signed-off-by: Naman Munet --- .../mgr/dashboard/controllers/rgw_iam.py | 16 +- .../rgw-user-accounts-form.component.html | 12 +- .../rgw-user-accounts-form.component.ts | 150 +++++++++++++----- .../rgw-user-accounts.component.ts | 15 +- .../frontend/src/app/ceph/rgw/rgw.module.ts | 5 + .../shared/api/rgw-user-accounts.service.ts | 4 + src/pybind/mgr/dashboard/openapi.yaml | 17 +- src/pybind/mgr/dashboard/services/rgw_iam.py | 84 +++++----- 8 files changed, 216 insertions(+), 87 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/rgw_iam.py b/src/pybind/mgr/dashboard/controllers/rgw_iam.py index 1a293a7a17a..80d4b85862f 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw_iam.py +++ b/src/pybind/mgr/dashboard/controllers/rgw_iam.py @@ -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'), diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html index 279d36de94c..b9ccddd0a56 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html @@ -15,7 +15,7 @@ Account Name + formControlName="name"/> - This field is required. @@ -113,12 +113,12 @@ cdsRow>
- +
- - + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.ts index 7d2734b61f6..8e0d8183566 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.ts @@ -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) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts index 882a3f40cd2..0ad449761c1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts/rgw-user-accounts.component.ts @@ -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) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index 012e7c2fd21..f75c86607a9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -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 } } ] }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts index 971261013af..af53724d47d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user-accounts.service.ts @@ -30,6 +30,10 @@ export class RgwUserAccountsService { return this.http.post(this.url, payload); } + modify(payload: any): Observable { + return this.http.put(`${this.url}/set`, payload); + } + setQuota( account_id: string, payload: { quota_type: string; max_size: string; max_objects: string; enabled: boolean } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index c548ec32c0d..d744eb6c42c 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -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': diff --git a/src/pybind/mgr/dashboard/services/rgw_iam.py b/src/pybind/mgr/dashboard/services/rgw_iam.py index 43a3f2be181..896f1a93239 100644 --- a/src/pybind/mgr/dashboard/services/rgw_iam.py +++ b/src/pybind/mgr/dashboard/services/rgw_iam.py @@ -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 -- 2.39.5