@APIRouter('rgw/accounts', Scope.RGW)
@APIDoc("RGW User Accounts API", "RgwUserAccounts")
class RgwUserAccountsController(RESTController):
-
+ @EndpointDoc("Update RGW account info",
+ parameters={'account_name': (str, 'Account name'),
+ 'email': (str, 'Email'),
+ 'tenant': (str, 'Tenant'),
+ 'max_buckets': (int, 'Max buckets'),
+ 'max_users': (int, 'Max users'),
+ 'max_roles': (int, 'Max roles'),
+ 'max_group': (int, 'Max groups'),
+ 'max_access_keys': (int, 'Max access keys')})
@allow_empty_body
- def create(self, account_name: Optional[str] = None, tenant: 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):
+ def create(self, account_name: str, tenant: Optional[str] = None,
+ email: Optional[str] = None, max_buckets: Optional[int] = None,
+ max_users: Optional[int] = None, max_roles: Optional[int] = None,
+ max_group: Optional[int] = None,
+ max_access_keys: Optional[int] = None):
+ """
+ Create an account
+
+ :param account_name: Account name
+ :return: Returns account resource.
+ :rtype: Dict[str, Any]
+ """
return RgwAccounts.create_account(account_name, tenant, email,
max_buckets, max_users, max_roles,
max_group, max_access_keys)
def list(self, detailed: bool = False):
+ """
+ List all account ids or all detailed account info based on the 'detailed' query parameter.
+
+ - If detailed=True, returns detailed account info.
+ - If detailed=False, returns only account ids.
+ """
detailed = str_to_bool(detailed)
return RgwAccounts.get_accounts(detailed)
@EndpointDoc("Get RGW Account by id",
parameters={'account_id': (str, 'Account id')})
def get(self, account_id: str):
+ """
+ Get an account by account id
+ """
return RgwAccounts.get_account(account_id)
@EndpointDoc("Delete RGW Account",
parameters={'account_id': (str, 'Account id')})
def delete(self, account_id):
+ """
+ Removes an account
+
+ :param account_id: account identifier
+ :return: None.
+ """
return RgwAccounts.delete_account(account_id)
@EndpointDoc("Update RGW account info",
- parameters={'account_id': (str, 'Account id')})
+ parameters={'account_id': (str, 'Account id'),
+ 'account_name': (str, 'Account name'),
+ 'email': (str, 'Email'),
+ 'tenant': (str, 'Tenant'),
+ 'max_buckets': (int, 'Max buckets'),
+ 'max_users': (int, 'Max users'),
+ 'max_roles': (int, 'Max roles'),
+ 'max_group': (int, 'Max groups'),
+ 'max_access_keys': (int, 'Max access keys')})
@allow_empty_body
- def set(self, account_id: str, 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):
+ def set(self, account_id: str, account_name: str,
+ email: Optional[str] = None, tenant: Optional[str] = None,
+ max_buckets: Optional[int] = None, max_users: Optional[int] = None,
+ max_roles: Optional[int] = None, max_group: Optional[int] = None,
+ max_access_keys: Optional[int] = None):
+ """
+ Modifies an account
+
+ :param account_id: Account identifier
+ :return: Returns modified account resource.
+ :rtype: Dict[str, Any]
+ """
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'),
- 'max_size': (str, 'Max size')})
+ 'quota_type': (str, 'Quota type'),
+ 'max_size': (str, 'Max size'),
+ 'max_objects': (str, 'Max objects')})
@RESTController.Resource(method='PUT', path='/quota')
@allow_empty_body
def set_quota(self, quota_type: str, account_id: str, max_size: str, max_objects: str,
enabled: bool):
+ """
+ Modifies quota
+
+ :param account_id: Account identifier
+ :param quota_type: 'account' or 'bucket'
+ :return: Returns modified quota.
+ :rtype: Dict[str, Any]
+ """
return RgwAccounts.set_quota(quota_type, account_id, max_size, max_objects, enabled)
@EndpointDoc("Enable/Disable RGW Account/Bucket quota",
- parameters={'account_id': (str, 'Account id')})
+ parameters={'account_id': (str, 'Account id'),
+ 'quota_type': (str, 'Quota type'),
+ 'quota_status': (str, 'Quota status')})
@RESTController.Resource(method='PUT', path='/quota/status')
@allow_empty_body
def set_quota_status(self, quota_type: str, account_id: str, quota_status: str):
+ """
+ Enable/Disable quota
+
+ :param account_id: Account identifier
+ :param quota_type: 'account' or 'bucket'
+ :param quota_status: 'enable' or 'disable'
+ :return: Returns modified quota.
+ :rtype: Dict[str, Any]
+ """
return RgwAccounts.set_quota_status(quota_type, account_id, quota_status)
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RgwUserAccountsDetailsComponent } from './rgw-user-accounts-details.component';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { TableKeyValueComponent } from '~/app/shared/datatable/table-key-value/table-key-value.component';
+import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
describe('RgwUserAccountsDetailsComponent', () => {
let component: RgwUserAccountsDetailsComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [RgwUserAccountsDetailsComponent]
+ declarations: [RgwUserAccountsDetailsComponent, TableKeyValueComponent],
+ providers: [DimlessBinaryPipe, CdDatePipe]
}).compileComponents();
fixture = TestBed.createComponent(RgwUserAccountsDetailsComponent);
component = fixture.componentInstance;
+ component.selection = { quota: {}, bucket_quota: {} };
fixture.detectChanges();
});
createDisplayValues(quota_type: string) {
return {
Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
- 'Maximum Size':
- this.selection[quota_type].max_size <= -1
+ 'Maximum size': this.selection[quota_type].enabled
+ ? this.selection[quota_type].max_size <= -1
? 'Unlimited'
- : this.dimlessBinary.transform(this.selection[quota_type].max_size),
- 'Maximum objects':
- this.selection[quota_type].max_objects <= -1
+ : this.dimlessBinary.transform(this.selection[quota_type].max_size)
+ : '-',
+ 'Maximum objects': this.selection[quota_type].enabled
+ ? this.selection[quota_type].max_objects <= -1
? 'Unlimited'
: this.selection[quota_type].max_objects
+ : '-'
};
}
}
[name]="formControl"
[id]="formControl"
label="{{formControl.split('_')[1] | upperFirst}} Mode"
- (change)="onModeChange($event.target.value, formControl)">
- <option value="-1">Disabled</option>
+ (change)="onModeChange($event.target.value, formControl)"
+ [helperText]="getHelperTextForMode(formControl)">
<option value="0">Unlimited</option>
+ <option value="-1">Disabled</option>
<option value="1">Custom</option>
</cds-select>
</ng-template>
formControlName="{{formControl}}"
label="{{formControl.split('_')[0] | upperFirst}}. {{formControl.split('_').length > 2 ? formControl.split('_')[1]+' '+formControl.split('_')[2]: formControl.split('_')[1]}}"
[min]="1"
+ cdRequiredField="{{formControl.split('_')[0] | upperFirst}}. {{formControl.split('_').length > 2 ? formControl.split('_')[1]+' '+formControl.split('_')[2]: formControl.split('_')[1]}}"
[invalid]="!accountForm.controls[formControl].valid && accountForm.controls[formControl].dirty"
[invalidText]="maxValError"></cds-number>
<ng-template #maxValError>
<span *ngIf="accountForm.showError(formControl, formDir, 'required')"
i18n>This field is required.</span>
+ <span *ngIf="accountForm.controls[formControl].value == 0 && accountForm.showError(formControl, formDir, 'min')"
+ i18n>Enter number greater than 0</span>
<span *ngIf="accountForm.showError(formControl, formDir, 'pattern')"
- i18n>The entered value must be a number greater than 0</span>
+ i18n>Enter a valid positive number</span>
</ng-template>
</ng-template>
[formGroup]="accountForm">
<fieldset class="cds--fieldset">
<legend class="cds--label">{{quotaType | upperFirst}} Quota</legend>
+ <div *ngIf="quotaType == 'account';else bucket"
+ class="quota-heading">Set quota on account owned by users.</div>
+ <ng-template #bucket>
+ <div class="quota-heading">
+ Set quota on buckets owned by an account.
+ </div>
+ </ng-template>
<!-- Enabled -->
<cds-checkbox [formControlName]="formControl.enabled">
Enabled
</cds-checkbox >
- <!-- Unlimited size -->
- <cds-checkbox *ngIf="accountForm.controls[formControl.enabled].value"
- [formControlName]="formControl.unlimitedSize">
- Unlimited size
- </cds-checkbox>
- <!-- Maximum size -->
- <div class="form-item"
- *ngIf="accountForm.controls[formControl.enabled].value && !accountForm.getValue(formControl.unlimitedSize)">
- <cds-text-label [label]="formControl.maxSize"
- [for]="formControl.maxSize"
- [invalid]="!accountForm.controls[formControl.maxSize].valid && accountForm.controls[formControl.maxSize].dirty"
- [invalidText]="quotaSizeError"
- i18n>Max. size
- <input cdsText
- type="text"
- placeholder="Enter size"
- [id]="formControl.maxSize"
- [name]="formControl.maxSize"
- [formControlName]="formControl.maxSize"
- cdDimlessBinary/>
- </cds-text-label>
- <ng-template #quotaSizeError>
- <span *ngIf="accountForm.showError(formControl.maxSize, formDir, 'required')"
- i18n>This field is required.</span>
- <span *ngIf="accountForm.showError(formControl.maxSize, formDir, 'quotaMaxSize')"
- i18n>The value is not valid.</span>
- <span *ngIf="accountForm.showError(formControl.maxSize, formDir, 'pattern')"
- i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
- </ng-template>
- </div>
- <!-- Unlimited objects -->
- <cds-checkbox *ngIf="accountForm.controls[formControl.enabled].value"
- [formControlName]="formControl.unlimitedObjects">
- Unlimited objects
- </cds-checkbox>
- <!-- Maximum objects -->
- <div class="form-item"
- *ngIf="accountForm.controls[formControl.enabled].value && !accountForm.getValue(formControl.unlimitedObjects)">
- <cds-text-label [label]="formControl.maxObjects"
- [for]="formControl.maxObjects"
- [invalid]="!accountForm.controls[formControl.maxObjects].valid && accountForm.controls[formControl.maxObjects].dirty"
- [invalidText]="quotaObjectError"
- i18n>Max. objects
- <input cdsText
- type="number"
- placeholder="Enter number of objects"
- [id]="formControl.maxObjects"
- [name]="formControl.maxObjects"
- [formControlName]="formControl.maxObjects"/>
- </cds-text-label>
- <ng-template #quotaObjectError>
- <span *ngIf="accountForm.showError(formControl.maxObjects, formDir, 'required')"
- i18n>This field is required.</span>
- <span *ngIf="accountForm.showError(formControl.maxObjects, formDir, 'pattern')"
- i18n>Please enter a valid number</span>
- </ng-template>
+ <div class="quota-sub-block"
+ *ngIf="accountForm.controls[formControl.enabled].value">
+ <!-- Unlimited size -->
+ <cds-checkbox *ngIf="accountForm.controls[formControl.enabled].value"
+ [formControlName]="formControl.unlimitedSize">
+ Unlimited size
+ </cds-checkbox>
+ <!-- Maximum size -->
+ <div class="input-wrapper"
+ *ngIf="accountForm.controls[formControl.enabled].value && !accountForm.getValue(formControl.unlimitedSize)">
+ <cds-text-label [label]="formControl.maxSize"
+ [for]="formControl.maxSize"
+ cdRequiredField="Max. size"
+ [invalid]="!accountForm.controls[formControl.maxSize].valid && accountForm.controls[formControl.maxSize].dirty"
+ [invalidText]="quotaSizeError"
+ i18n>Max. size
+ <input cdsText
+ type="text"
+ placeholder="Enter size"
+ [id]="formControl.maxSize"
+ [name]="formControl.maxSize"
+ [formControlName]="formControl.maxSize"
+ cdDimlessBinary/>
+ </cds-text-label>
+ <ng-template #quotaSizeError>
+ <span *ngIf="accountForm.showError(formControl.maxSize, formDir, 'required')"
+ i18n>This field is required.</span>
+ <span *ngIf="accountForm.showError(formControl.maxSize, formDir, 'quotaMaxSize')"
+ i18n>Enter a valid value.</span>
+ <span *ngIf="accountForm.showError(formControl.maxSize, formDir, 'pattern')"
+ i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
+ </ng-template>
+ </div>
+ <!-- Unlimited objects -->
+ <cds-checkbox *ngIf="accountForm.controls[formControl.enabled].value"
+ [formControlName]="formControl.unlimitedObjects">
+ Unlimited objects
+ </cds-checkbox>
+ <!-- Maximum objects -->
+ <div class="input-wrapper"
+ *ngIf="accountForm.controls[formControl.enabled].value && !accountForm.getValue(formControl.unlimitedObjects)">
+ <cds-text-label [label]="formControl.maxObjects"
+ [for]="formControl.maxObjects"
+ cdRequiredField="Max. objects"
+ [invalid]="!accountForm.controls[formControl.maxObjects].valid && accountForm.controls[formControl.maxObjects].dirty"
+ [invalidText]="quotaObjectError"
+ i18n>Max. objects
+ <input cdsText
+ type="number"
+ placeholder="Enter number of objects"
+ [id]="formControl.maxObjects"
+ [name]="formControl.maxObjects"
+ [formControlName]="formControl.maxObjects"/>
+ </cds-text-label>
+ <ng-template #quotaObjectError>
+ <span *ngIf="accountForm.showError(formControl.maxObjects, formDir, 'required')"
+ i18n>This field is required.</span>
+ <span *ngIf="accountForm.showError(formControl.maxObjects, formDir, 'pattern')"
+ i18n>Enter a valid positive number</span>
+ </ng-template>
+ </div>
</div>
</fieldset>
</ng-template>
+@use '@carbon/layout';
+
+::ng-deep .cds--form__helper-text {
+ white-space: pre-line;
+}
+
+.quota-heading {
+ margin-bottom: layout.$spacing-03;
+}
+
+.quota-sub-block {
+ margin: layout.$spacing-03 0 0 layout.$spacing-06;
+
+ .input-wrapper {
+ margin: layout.$spacing-03 0 0 layout.$spacing-01;
+
+ &:first-of-type {
+ margin-bottom: layout.$spacing-06;
+ }
+ }
+}
import { of } from 'rxjs';
import { RgwUserAccountsService } from '~/app/shared/api/rgw-user-accounts.service';
import { ModalModule } from 'carbon-components-angular';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RgwUserAccountsComponent } from '../rgw-user-accounts/rgw-user-accounts.component';
class MockRgwUserAccountsService {
create = jest.fn().mockReturnValue(of(null));
+ modify = jest.fn().mockReturnValue(of(null));
+ setQuota = jest.fn().mockReturnValue(of(null));
}
describe('RgwUserAccountsFormComponent', () => {
ToastrModule.forRoot(),
HttpClientTestingModule,
PipesModule,
- RouterTestingModule,
- ModalModule
+ RouterTestingModule.withRoutes([
+ { path: 'rgw/accounts', component: RgwUserAccountsComponent }
+ ]),
+ ModalModule,
+ ReactiveFormsModule
],
providers: [{ provide: RgwUserAccountsService, useClass: MockRgwUserAccountsService }]
}).compileComponents();
it('should call create method of MockRgwUserAccountsService and show success notification', () => {
component.editing = false;
+ component.accountForm.get('name').setValue('test');
+ const payload = {
+ account_name: 'test',
+ email: '',
+ tenant: '',
+ max_users: 1000,
+ max_buckets: 1000,
+ max_roles: 1000,
+ max_group: 1000,
+ max_access_keys: 4
+ };
const spy = jest.spyOn(component, 'submit');
const createDataSpy = jest.spyOn(rgwUserAccountsService, 'create').mockReturnValue(of(null));
component.submit();
+ expect(component.accountForm.valid).toBe(true);
expect(spy).toHaveBeenCalled();
expect(createDataSpy).toHaveBeenCalled();
+ expect(createDataSpy).toHaveBeenCalledWith(payload);
+ });
+
+ it('should call modify method of MockRgwUserAccountsService and show success notification', () => {
+ component.editing = true;
+ component.accountForm.get('name').setValue('test');
+ component.accountForm.get('id').setValue('RGW12312312312312312');
+ component.accountForm.get('email').setValue('test@test.com');
+ const payload = {
+ account_id: 'RGW12312312312312312',
+ account_name: 'test',
+ email: 'test@test.com',
+ tenant: '',
+ max_users: 1000,
+ max_buckets: 1000,
+ max_roles: 1000,
+ max_group: 1000,
+ max_access_keys: 4
+ };
+ const spy = jest.spyOn(component, 'submit');
+ const modifyDataSpy = jest.spyOn(rgwUserAccountsService, 'modify').mockReturnValue(of(null));
+ component.submit();
+ expect(component.accountForm.valid).toBe(true);
+ expect(spy).toHaveBeenCalled();
+ expect(modifyDataSpy).toHaveBeenCalled();
+ expect(modifyDataSpy).toHaveBeenCalledWith(payload);
+ });
+
+ it('should call setQuota for "account" if account quota is dirty', () => {
+ component.accountForm.get('id').setValue('123');
+ component.accountForm.get('account_quota_enabled').setValue(true);
+ component.accountForm.get('account_quota_max_size_unlimited').setValue(false);
+ component.accountForm.get('account_quota_max_size').setValue('1 GiB');
+ component.accountForm.get('account_quota_max_objects_unlimited').setValue(false);
+ component.accountForm.get('account_quota_max_objects').setValue('100');
+ component.accountForm.get('account_quota_max_size').markAsDirty();
+ const accountId = '123';
+
+ const spySetQuota = jest.spyOn(rgwUserAccountsService, 'setQuota').mockReturnValue(of(null));
+ const spyGoToListView = jest.spyOn(component, 'goToListView');
+
+ component.setQuotaConfig();
+ const accountQuotaArgs = {
+ quota_type: 'account',
+ enabled: component.accountForm.getValue('account_quota_enabled'),
+ max_size: '1073741824',
+ max_objects: component.accountForm.getValue('account_quota_max_objects')
+ };
+ expect(spySetQuota).toHaveBeenCalledWith(accountId, accountQuotaArgs);
+ expect(spyGoToListView).toHaveBeenCalled();
+ });
+
+ it('should call setQuota for "bucket" if account quota is dirty', () => {
+ component.accountForm.get('id').setValue('123');
+ component.accountForm.get('bucket_quota_enabled').setValue(true);
+ component.accountForm.get('bucket_quota_max_size_unlimited').setValue(false);
+ component.accountForm.get('bucket_quota_max_size').setValue('1 GiB');
+ component.accountForm.get('bucket_quota_max_objects_unlimited').setValue(false);
+ component.accountForm.get('bucket_quota_max_objects').setValue('100');
+ component.accountForm.get('bucket_quota_max_size').markAsDirty();
+ const accountId = '123';
+
+ const spySetQuota = jest.spyOn(rgwUserAccountsService, 'setQuota').mockReturnValue(of(null));
+ const spyGoToListView = jest.spyOn(component, 'goToListView');
+
+ component.setQuotaConfig();
+ const bucketQuotaArgs = {
+ quota_type: 'bucket',
+ enabled: component.accountForm.getValue('bucket_quota_enabled'),
+ max_size: '1073741824',
+ max_objects: component.accountForm.getValue('bucket_quota_max_objects')
+ };
+ expect(spySetQuota).toHaveBeenCalledWith(accountId, bucketQuotaArgs);
+ expect(spyGoToListView).toHaveBeenCalled();
});
});
mapValuesForMode(value: any, formControlName: string) {
switch (value[formControlName]) {
case -1:
- value[`${formControlName}_mode`] = -1;
+ value[`${formControlName}_mode`] = '-1';
value[formControlName] = '';
break;
case 0:
- value[`${formControlName}_mode`] = 0;
+ value[`${formControlName}_mode`] = '0';
value[formControlName] = '';
break;
default:
- value[`${formControlName}_mode`] = 1;
+ value[`${formControlName}_mode`] = '1';
break;
}
}
tenant: [''],
name: ['', Validators.required],
email: ['', CdValidators.email],
- max_users_mode: [1],
+ max_users_mode: ['1'],
max_users: [
1000,
- [CdValidators.requiredIf({ max_users_mode: '1' }), CdValidators.number(false)]
+ [
+ CdValidators.requiredIf({ max_users_mode: '1' }),
+ CdValidators.number(false),
+ Validators.min(1)
+ ]
],
- max_roles_mode: [1],
+ max_roles_mode: ['1'],
max_roles: [
1000,
- [CdValidators.requiredIf({ max_roles_mode: '1' }), CdValidators.number(false)]
+ [
+ CdValidators.requiredIf({ max_roles_mode: '1' }),
+ CdValidators.number(false),
+ Validators.min(1)
+ ]
],
- max_groups_mode: [1],
+ max_groups_mode: ['1'],
max_groups: [
1000,
- [CdValidators.requiredIf({ max_groups_mode: '1' }), CdValidators.number(false)]
+ [
+ CdValidators.requiredIf({ max_groups_mode: '1' }),
+ CdValidators.number(false),
+ Validators.min(1)
+ ]
],
- max_access_keys_mode: [1],
+ max_access_keys_mode: ['1'],
max_access_keys: [
4,
- [CdValidators.requiredIf({ max_access_keys_mode: '1' }), CdValidators.number(false)]
+ [
+ CdValidators.requiredIf({ max_access_keys_mode: '1' }),
+ CdValidators.number(false),
+ Validators.min(1)
+ ]
],
- max_buckets_mode: [1],
+ max_buckets_mode: ['1'],
max_buckets: [
1000,
- [CdValidators.requiredIf({ max_buckets_mode: '1' }), CdValidators.number(false)]
+ [
+ CdValidators.requiredIf({ max_buckets_mode: '1' }),
+ CdValidators.number(false),
+ Validators.min(1)
+ ]
],
account_quota_enabled: [false],
account_quota_max_size_unlimited: [true],
this.accountForm.getValue(`${quotaType}_quota_max_size`)
);
// Finally convert the value to KiB.
- result['max_size'] = (bytes / 1024).toFixed(0) as any;
+ result['max_size'] = bytes.toFixed(0) as any;
}
if (!this.accountForm.getValue(`${quotaType}_quota_max_objects_unlimited`)) {
result['max_objects'] = `${this.accountForm.getValue(`${quotaType}_quota_max_objects`)}`;
? formvalue[formControlName]
: formvalue[`${formControlName}_mode`];
}
+
+ getHelperTextForMode(formControl: string) {
+ const resourceName =
+ formControl.split('_').length > 3
+ ? formControl.split('_')[1] + ' ' + formControl.split('_')[2]
+ : formControl.split('_')[1];
+ if (this.accountForm.getValue(formControl) == -1) {
+ return `${resourceName[0].toUpperCase() + resourceName.slice(1, -1)} creation is disabled.`;
+ } else if (this.accountForm.getValue(formControl) == 0) {
+ return `Unlimited ${resourceName.slice(0, -1)} creation allowed.`;
+ }
+ return '';
+ }
}
<legend i18n>
User Accounts
<cd-help-text>
- This feature allows administrators to assign unique credentials to individual users or applications,
- ensuring granular access control and improved security across the cluster.
+ Administrators can assign unique credentials to users or applications, enabling granular access control and enhancing security across the cluster.
</cd-help-text>
</legend>
<cd-table #table
(setExpandedRow)="setExpandedRow($event)"
(updateSelection)="updateSelection($event)"
identifier="id"
- (fetchData)="getAccountsList($event)">
+ (fetchData)="getAccountsList($event)"
+ [status]="tableStatus">
<cd-table-actions class="table-actions"
[permission]="permission"
[selection]="selection"
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, NgZone, OnInit, ViewChild } from '@angular/core';
import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
import { TableComponent } from '~/app/shared/datatable/table/table.component';
import { Observable, Subscriber, forkJoin as observableForkJoin } from 'rxjs';
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
import { FinishedTask } from '~/app/shared/models/finished-task';
+import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
const BASE_URL = 'rgw/accounts';
columns: CdTableColumn[] = [];
accounts: Account[] = [];
selection: CdTableSelection = new CdTableSelection();
+ declare staleTimeout: number;
constructor(
private authStorageService: AuthStorageService,
private router: Router,
private rgwUserAccountsService: RgwUserAccountsService,
private cdsModalService: ModalCdsService,
- private taskWrapper: TaskWrapperService
+ private taskWrapper: TaskWrapperService,
+ protected ngZone: NgZone
) {
super();
}
this.permission = this.authStorageService.getPermissions().rgw;
this.columns = [
{
- name: $localize`Account Id`,
- prop: 'id',
+ name: $localize`Name`,
+ prop: 'name',
flexGrow: 1
},
{
flexGrow: 1
},
{
- name: $localize`Full name`,
- prop: 'name',
+ name: $localize`Account id`,
+ prop: 'id',
flexGrow: 1
},
{
flexGrow: 1
},
{
- name: $localize`Max Users`,
+ name: $localize`Max users`,
prop: 'max_users',
- flexGrow: 1
+ flexGrow: 1,
+ cellTransformation: CellTemplate.map,
+ customTemplateConfig: {
+ '-1': $localize`Disabled`,
+ 0: $localize`Unlimited`
+ }
},
{
- name: $localize`Max Roles`,
+ name: $localize`Max roles`,
prop: 'max_roles',
- flexGrow: 1
+ flexGrow: 1,
+ cellTransformation: CellTemplate.map,
+ customTemplateConfig: {
+ '-1': $localize`Disabled`,
+ 0: $localize`Unlimited`
+ }
},
{
- name: $localize`Max Groups`,
+ name: $localize`Max groups`,
prop: 'max_groups',
- flexGrow: 1
+ flexGrow: 1,
+ cellTransformation: CellTemplate.map,
+ customTemplateConfig: {
+ '-1': $localize`Disabled`,
+ 0: $localize`Unlimited`
+ }
},
{
name: $localize`Max. buckets`,
prop: 'max_buckets',
- flexGrow: 1
+ flexGrow: 1,
+ cellTransformation: CellTemplate.map,
+ customTemplateConfig: {
+ '-1': $localize`Disabled`,
+ 0: $localize`Unlimited`
+ }
},
{
- name: $localize`Max Access Keys`,
+ name: $localize`Max access keys`,
prop: 'max_access_keys',
- flexGrow: 1
+ flexGrow: 1,
+ cellTransformation: CellTemplate.map,
+ customTemplateConfig: {
+ '-1': $localize`Disabled`,
+ 0: $localize`Unlimited`
+ }
}
];
const getEditURL = () => {
name: this.actionLabels.DELETE
};
this.tableActions = [addAction, editAction, deleteAction];
+ this.setTableRefreshTimeout();
}
getAccountsList(context?: CdTableFetchDataContext) {
+ this.setTableRefreshTimeout();
this.rgwUserAccountsService.list(true).subscribe({
next: (accounts: Account[]) => {
this.accounts = accounts;
- Prometheus
/api/rgw/accounts:
get:
+ description: "\n List all account ids or all detailed account info based\
+ \ on the 'detailed' query parameter.\n\n - If detailed=True, returns\
+ \ detailed account info.\n - If detailed=False, returns only account\
+ \ ids.\n "
parameters:
- default: false
in: query
tags:
- RgwUserAccounts
post:
+ description: "\n Create an account\n\n :param account_name: Account\
+ \ name\n :return: Returns account resource.\n :rtype: Dict[str,\
+ \ Any]\n "
parameters: []
requestBody:
content:
schema:
properties:
account_name:
- type: integer
+ description: Account name
+ type: string
email:
+ description: Email
type: string
max_access_keys:
- type: string
+ description: Max access keys
+ type: integer
max_buckets:
- type: string
+ description: Max buckets
+ type: integer
max_group:
- type: string
+ description: Max groups
+ type: integer
max_roles:
- type: string
+ description: Max roles
+ type: integer
max_users:
- type: string
+ description: Max users
+ type: integer
tenant:
+ description: Tenant
type: string
+ required:
+ - account_name
type: object
responses:
'201':
trace.
security:
- jwt: []
+ summary: Update RGW account info
tags:
- RgwUserAccounts
/api/rgw/accounts/{account_id}:
delete:
+ description: "\n Removes an account\n\n :param account_id: account\
+ \ identifier\n :return: None.\n "
parameters:
- description: Account id
in: path
tags:
- RgwUserAccounts
get:
+ description: "\n Get an account by account id\n "
parameters:
- description: Account id
in: path
tags:
- RgwUserAccounts
put:
+ description: "\n Modifies an account\n\n :param account_id: Account\
+ \ identifier\n :return: Returns modified account resource.\n \
+ \ :rtype: Dict[str, Any]\n "
parameters:
- description: Account id
in: path
schema:
properties:
account_name:
- type: integer
+ description: Account name
+ type: string
email:
+ description: Email
type: string
max_access_keys:
- type: string
+ description: Max access keys
+ type: integer
max_buckets:
- type: string
+ description: Max buckets
+ type: integer
max_group:
- type: string
+ description: Max groups
+ type: integer
max_roles:
- type: string
+ description: Max roles
+ type: integer
max_users:
- type: string
+ description: Max users
+ type: integer
tenant:
+ description: Tenant
type: string
+ required:
+ - account_name
type: object
responses:
'200':
- RgwUserAccounts
/api/rgw/accounts/{account_id}/quota:
put:
+ description: "\n Modifies quota\n\n :param account_id: Account\
+ \ identifier\n :param quota_type: 'account' or 'bucket'\n :return:\
+ \ Returns modified quota.\n :rtype: Dict[str, Any]\n "
parameters:
- description: Account id
in: path
enabled:
type: string
max_objects:
+ description: Max objects
type: string
max_size:
description: Max size
type: string
quota_type:
+ description: Quota type
type: string
required:
- quota_type
- RgwUserAccounts
/api/rgw/accounts/{account_id}/quota/status:
put:
+ description: "\n Enable/Disable quota\n\n :param account_id: Account\
+ \ identifier\n :param quota_type: 'account' or 'bucket'\n :param\
+ \ quota_status: 'enable' or 'disable'\n :return: Returns modified quota.\n\
+ \ :rtype: Dict[str, Any]\n "
parameters:
- description: Account id
in: path
schema:
properties:
quota_status:
+ description: Quota status
type: string
quota_type:
+ description: Quota type
type: string
required:
- quota_type
return cls.send_rgw_cmd(get_account_cmd)
@classmethod
- def create_account(cls, account_name: Optional[str] = None, tenant: 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):
+ def create_account(cls, account_name: str, tenant: Optional[str] = None,
+ email: Optional[str] = None, max_buckets: Optional[int] = None,
+ max_users: Optional[int] = None, max_roles: Optional[int] = None,
+ max_group: Optional[int] = None, max_access_keys: Optional[int] = None):
create_accounts_cmd = ['account', 'create']
create_accounts_cmd += cls.get_common_args_list(account_name, email,
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, tenant: str = None,
- max_buckets: str = None, max_users: str = None,
- max_roles: str = None, max_group: str = None,
- max_access_keys: str = None):
+ def modify_account(cls, account_id: str, account_name: str,
+ email: Optional[str] = None, tenant: Optional[str] = None,
+ max_buckets: Optional[int] = None, max_users: Optional[int] = None,
+ max_roles: Optional[int] = None, max_group: Optional[int] = None,
+ max_access_keys: Optional[int] = None):
modify_accounts_cmd = ['account', 'modify', '--account-id', account_id]
modify_accounts_cmd += cls.get_common_args_list(account_name, email,
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):
+ def get_common_args_list(cls, account_name: str, email: Optional[str] = None,
+ tenant: Optional[str] = None, max_buckets: Optional[int] = None,
+ max_users: Optional[int] = None, max_roles: Optional[int] = None,
+ max_group: Optional[int] = None,
+ max_access_keys: Optional[int] = None):
common_cmd_list = []
if account_name:
common_cmd_list += ['--account-name', account_name]
mock_create_account.return_value = mockReturnVal
controller = RgwUserAccountsController()
- result = controller.create(account_name='test_account', account_id='RGW18661471562806836',
- email='test@example.com')
+ result = controller.create(account_name='test_account', tenant='',
+ email='test@example.com', max_buckets=1000,
+ max_users=1000, max_roles=1000, max_group=1000,
+ max_access_keys=4)
# Check if the account creation method was called with the correct parameters
- mock_create_account.assert_called_with('test_account', 'RGW18661471562806836',
- 'test@example.com')
+ mock_create_account.assert_called_with('test_account', '', 'test@example.com',
+ 1000, 1000, 1000, 1000, 4)
# Check the returned result
self.assertEqual(result, mockReturnVal)
controller = RgwUserAccountsController()
result = controller.set(account_id='RGW59378973811515857', account_name='new_account_name',
- email='new_email@example.com')
+ email='new_email@example.com', tenant='', max_buckets=1000,
+ max_users=1000, max_roles=1000, max_group=1000, max_access_keys=4)
mock_modify_account.assert_called_with('RGW59378973811515857', 'new_account_name',
- 'new_email@example.com')
+ 'new_email@example.com', '', 1000, 1000, 1000,
+ 1000, 4)
self.assertEqual(result, mock_return_value)
controller = RgwUserAccountsController()
result = controller.set_quota(quota_type='account', account_id='RGW11111111111111111',
- max_size='10GB', max_objects='1000')
+ max_size='10GB', max_objects='1000', enabled=True)
- mock_set_quota.assert_called_with('account', 'RGW11111111111111111', '10GB', '1000')
+ mock_set_quota.assert_called_with('account', 'RGW11111111111111111', '10GB', '1000', True)
self.assertEqual(result, mock_return_value)