<tr>
<td i18n
class="bold col-sm-1">Maximum buckets</td>
- <td class="col-sm-3">{{ user.max_buckets }}</td>
+ <td class="col-sm-3">{{ user.max_buckets| map:maxBucketsMap }}</td>
</tr>
<tr *ngIf="user.subusers && user.subusers.length">
<td i18n
// Details tab
user: any;
+ maxBucketsMap: {};
// Keys tab
keys: any = [];
flexGrow: 1
}
];
+ this.maxBucketsMap = {
+ '-1': this.i18n('Disabled'),
+ 0: this.i18n('Unlimited')
+ };
}
ngOnChanges() {
</div>
<!-- Max. buckets -->
- <div class="form-group"
- [ngClass]="{'has-error': userForm.showError('max_buckets', frm)}">
+ <div class="form-group">
<label class="control-label col-sm-3"
- for="max_buckets">
- <ng-container i18n>Max. buckets</ng-container>
- <span class="required"></span>
- </label>
+ for="max_buckets_mode"
+ i18n>Max. buckets</label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="max_buckets_mode"
+ name="max_buckets_mode"
+ id="max_buckets_mode"
+ (change)="onMaxBucketsModeChange($event.target.value)">
+ <option i18n
+ value="-1">Disabled</option>
+ <option i18n
+ value="0">Unlimited</option>
+ <option i18n
+ value="1">Custom</option>
+ </select>
+ </div>
+ </div>
+ <div *ngIf="1 == userForm.get('max_buckets_mode').value"
+ class="form-group"
+ [ngClass]="{'has-error': userForm.showError('max_buckets', frm)}">
+ <label class="control-label col-sm-3"></label>
<div class="col-sm-9">
<input id="max_buckets"
class="form-control"
type="number"
- formControlName="max_buckets">
+ formControlName="max_buckets"
+ min="1">
<span class="help-block"
*ngIf="userForm.showError('max_buckets', frm, 'required')"
i18n>This field is required.</span>
<span class="help-block"
*ngIf="userForm.showError('max_buckets', frm, 'min')"
- i18n>The entered value must be >= 0.</span>
+ i18n>The entered value must be >= 1.</span>
</div>
</div>
}));
});
+ describe('max buckets', () => {
+ it('disable creation (create)', () => {
+ spyOn(rgwUserService, 'create');
+ formHelper.setValue('max_buckets_mode', -1, true);
+ component.onSubmit();
+ expect(rgwUserService.create).toHaveBeenCalledWith({
+ access_key: '',
+ display_name: null,
+ email: '',
+ generate_key: true,
+ max_buckets: -1,
+ secret_key: '',
+ suspended: false,
+ uid: null
+ });
+ });
+
+ it('disable creation (edit)', () => {
+ spyOn(rgwUserService, 'update');
+ component.editing = true;
+ formHelper.setValue('max_buckets_mode', -1, true);
+ component.onSubmit();
+ expect(rgwUserService.update).toHaveBeenCalledWith(null, {
+ display_name: null,
+ email: null,
+ max_buckets: -1,
+ suspended: false
+ });
+ });
+
+ it('unlimited buckets (create)', () => {
+ spyOn(rgwUserService, 'create');
+ formHelper.setValue('max_buckets_mode', 0, true);
+ component.onSubmit();
+ expect(rgwUserService.create).toHaveBeenCalledWith({
+ access_key: '',
+ display_name: null,
+ email: '',
+ generate_key: true,
+ max_buckets: 0,
+ secret_key: '',
+ suspended: false,
+ uid: null
+ });
+ });
+
+ it('unlimited buckets (edit)', () => {
+ spyOn(rgwUserService, 'update');
+ component.editing = true;
+ formHelper.setValue('max_buckets_mode', 0, true);
+ component.onSubmit();
+ expect(rgwUserService.update).toHaveBeenCalledWith(null, {
+ display_name: null,
+ email: null,
+ max_buckets: 0,
+ suspended: false
+ });
+ });
+
+ it('custom (create)', () => {
+ spyOn(rgwUserService, 'create');
+ formHelper.setValue('max_buckets_mode', 1, true);
+ formHelper.setValue('max_buckets', 100, true);
+ component.onSubmit();
+ expect(rgwUserService.create).toHaveBeenCalledWith({
+ access_key: '',
+ display_name: null,
+ email: '',
+ generate_key: true,
+ max_buckets: 100,
+ secret_key: '',
+ suspended: false,
+ uid: null
+ });
+ });
+
+ it('custom (edit)', () => {
+ spyOn(rgwUserService, 'update');
+ component.editing = true;
+ formHelper.setValue('max_buckets_mode', 1, true);
+ formHelper.setValue('max_buckets', 100, true);
+ component.onSubmit();
+ expect(rgwUserService.update).toHaveBeenCalledWith(null, {
+ display_name: null,
+ email: null,
+ max_buckets: 100,
+ suspended: false
+ });
+ });
+ });
+
describe('submit form', () => {
let notificationService: NotificationService;
[CdValidators.email],
[CdValidators.unique(this.rgwUserService.emailExists, this.rgwUserService)]
],
- max_buckets: [1000, [Validators.required, Validators.min(0)]],
+ max_buckets_mode: ['1'],
+ max_buckets: [
+ 1000,
+ [
+ CdValidators.requiredIf({ max_buckets_mode: '1' }),
+ CdValidators.number(false),
+ Validators.min(1)
+ ]
+ ],
suspended: [false],
// S3 key
generate_key: [true],
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'];
* @return {Boolean} Returns TRUE if the general user settings have been modified.
*/
private _isGeneralDirty(): boolean {
- return ['display_name', 'email', 'max_buckets', 'suspended'].some((path) => {
- return this.userForm.get(path).dirty;
- });
+ return ['display_name', 'email', 'max_buckets_mode', 'max_buckets', 'suspended'].some(
+ (path) => {
+ return this.userForm.get(path).dirty;
+ }
+ );
}
/**
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;
}
* configuration has been modified.
*/
private _getUpdateArgs() {
- const result = {};
+ 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;
}
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.
+ const maxBuckets = this.userForm.getValue('max_buckets');
+ this.userForm.patchValue({ max_buckets: _.isEmpty(maxBuckets) ? 1000 : maxBuckets });
+ }
+ }
}
{
name: this.i18n('Max. buckets'),
prop: 'max_buckets',
- flexGrow: 1
+ flexGrow: 1,
+ cellTransformation: CellTemplate.map,
+ customTemplateConfig: {
+ '-1': this.i18n('Disabled'),
+ 0: this.i18n('Unlimited')
+ }
}
];
const getUserUri = () =>
return this.http.get(`${this.url}/${uid}/quota`);
}
- create(args: object) {
+ create(args: Record<string, any>) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
return this.http.post(this.url, null, { params: params });
}
- update(uid: string, args: object) {
+ update(uid: string, args: Record<string, any>) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
let-value="value">
<span class="{{useCustomClass(value)}}">{{ value }}</span>
</ng-template>
+
+<ng-template #mapTpl
+ let-column="column"
+ let-value="value">
+ <span>{{ value | map:column?.customTemplateConfig }}</span>
+</ng-template>
executingTpl: TemplateRef<any>;
@ViewChild('classAddingTpl')
classAddingTpl: TemplateRef<any>;
+ @ViewChild('mapTpl')
+ mapTpl: TemplateRef<any>;
// This is the array with the items to be shown.
@Input()
this.cellTemplates.perSecond = this.perSecondTpl;
this.cellTemplates.executing = this.executingTpl;
this.cellTemplates.classAdding = this.classAddingTpl;
+ this.cellTemplates.map = this.mapTpl;
}
useCustomClass(value: any): string {
checkIcon = 'checkIcon',
routerLink = 'routerLink',
executing = 'executing',
- classAdding = 'classAdding'
+ classAdding = 'classAdding',
+ // Maps the value using the given dictionary.
+ // {
+ // ...
+ // cellTransformation: CellTemplate.map,
+ // customTemplateConfig: {
+ // [key: any]: any
+ // }
+ // }
+ map = 'map'
}
cellTransformation?: CellTemplate;
isHidden?: boolean;
prop: TableColumnProp; // Enforces properties to get sortable columns
+ customTemplateConfig?: any; // Custom configuration used by cell templates.
}
--- /dev/null
+import { MapPipe } from './map.pipe';
+
+describe('MapPipe', () => {
+ const pipe = new MapPipe();
+
+ it('create an instance', () => {
+ expect(pipe).toBeTruthy();
+ });
+
+ it('map value [1]', () => {
+ expect(pipe.transform('foo')).toBe('foo');
+ });
+
+ it('map value [2]', () => {
+ expect(pipe.transform('foo', { '-1': 'disabled', 0: 'unlimited' })).toBe('foo');
+ });
+
+ it('map value [3]', () => {
+ expect(pipe.transform(-1, { '-1': 'disabled', 0: 'unlimited' })).toBe('disabled');
+ });
+
+ it('map value [4]', () => {
+ expect(pipe.transform(0, { '-1': 'disabled', 0: 'unlimited' })).toBe('unlimited');
+ });
+});
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core';
+
+import * as _ from 'lodash';
+
+@Pipe({
+ name: 'map'
+})
+export class MapPipe implements PipeTransform {
+ transform(value: string | number, map?: object): any {
+ if (!_.isPlainObject(map)) {
+ return value;
+ }
+ return _.get(map, value, value);
+ }
+}
import { IscsiBackstorePipe } from './iscsi-backstore.pipe';
import { ListPipe } from './list.pipe';
import { LogPriorityPipe } from './log-priority.pipe';
+import { MapPipe } from './map.pipe';
import { MillisecondsPipe } from './milliseconds.pipe';
import { NotAvailablePipe } from './not-available.pipe';
import { OrdinalPipe } from './ordinal.pipe';
IopsPipe,
UpperFirstPipe,
RbdConfigurationSourcePipe,
- DurationPipe
+ DurationPipe,
+ MapPipe
],
exports: [
BooleanTextPipe,
IopsPipe,
UpperFirstPipe,
RbdConfigurationSourcePipe,
- DurationPipe
+ DurationPipe,
+ MapPipe
],
providers: [
BooleanTextPipe,
IopsPipe,
MillisecondsPipe,
NotAvailablePipe,
- UpperFirstPipe
+ UpperFirstPipe,
+ MapPipe
]
})
export class PipesModule {}