import { Component, EventEmitter, OnInit } from '@angular/core';
import { FormControl, ValidatorFn, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { I18n } from '@ngx-translate/i18n-polyfill';
import * as _ from 'lodash';
import { Observable } from 'rxjs';
import { PoolService } from '../../../shared/api/pool.service';
import { RbdService } from '../../../shared/api/rbd.service';
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import {
RbdConfigurationEntry,
RbdConfigurationSourceField
} from '../../../shared/models/configuration';
import { FinishedTask } from '../../../shared/models/finished-task';
import { Permission } from '../../../shared/models/permissions';
import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
import { AuthStorageService } from '../../../shared/services/auth-storage.service';
import { FormatterService } from '../../../shared/services/formatter.service';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model';
import { RbdFormCopyRequestModel } from './rbd-form-copy-request.model';
import { RbdFormCreateRequestModel } from './rbd-form-create-request.model';
import { RbdFormEditRequestModel } from './rbd-form-edit-request.model';
import { RbdFormMode } from './rbd-form-mode.enum';
import { RbdFormResponseModel } from './rbd-form-response.model';
@Component({
selector: 'cd-rbd-form',
templateUrl: './rbd-form.component.html',
styleUrls: ['./rbd-form.component.scss']
})
export class RbdFormComponent implements OnInit {
poolPermission: Permission;
rbdForm: CdFormGroup;
featuresFormGroups: CdFormGroup;
deepFlattenFormControl: FormControl;
layeringFormControl: FormControl;
exclusiveLockFormControl: FormControl;
objectMapFormControl: FormControl;
journalingFormControl: FormControl;
fastDiffFormControl: FormControl;
getDirtyConfigurationValues: (
includeLocalField?: boolean,
localField?: RbdConfigurationSourceField
) => RbdConfigurationEntry[];
pools: Array<string> = null;
allPools: Array<string> = null;
dataPools: Array<string> = null;
allDataPools: Array<string> = null;
features: any;
featuresList = [];
initializeConfigData = new EventEmitter<{
initialData: RbdConfigurationEntry[];
sourceType: RbdConfigurationSourceField;
}>();
pool: string;
advancedEnabled = false;
public rbdFormMode = RbdFormMode;
mode: RbdFormMode;
response: RbdFormResponseModel;
snapName: string;
defaultObjectSize = '4 MiB';
objectSizes: Array<string> = [
'4 KiB',
'8 KiB',
'16 KiB',
'32 KiB',
'64 KiB',
'128 KiB',
'256 KiB',
'512 KiB',
'1 MiB',
'2 MiB',
'4 MiB',
'8 MiB',
'16 MiB',
'32 MiB'
];
action: string;
resource: string;
constructor(
private authStorageService: AuthStorageService,
private route: ActivatedRoute,
private router: Router,
private poolService: PoolService,
private rbdService: RbdService,
private formatter: FormatterService,
private taskWrapper: TaskWrapperService,
private dimlessBinaryPipe: DimlessBinaryPipe,
private i18n: I18n,
public actionLabels: ActionLabelsI18n
) {
this.poolPermission = this.authStorageService.getPermissions().pool;
this.resource = this.i18n('RBD');
this.features = {
'deep-flatten': {
desc: this.i18n('Deep flatten'),
requires: null,
allowEnable: false,
allowDisable: true
},
layering: {
desc: this.i18n('Layering'),
requires: null,
allowEnable: false,
allowDisable: false
},
'exclusive-lock': {
desc: this.i18n('Exclusive lock'),
requires: null,
allowEnable: true,
allowDisable: true
},
'object-map': {
desc: this.i18n('Object map (requires exclusive-lock)'),
requires: 'exclusive-lock',
allowEnable: true,
allowDisable: true
},
journaling: {
desc: this.i18n('Journaling (requires exclusive-lock)'),
requires: 'exclusive-lock',
allowEnable: true,
allowDisable: true
},
'fast-diff': {
desc: this.i18n('Fast diff (requires object-map)'),
requires: 'object-map',
allowEnable: true,
allowDisable: true
}
};
this.createForm();
for (const key of Object.keys(this.features)) {
const listItem = this.features[key];
listItem.key = key;
this.featuresList.push(listItem);
}
}
createForm() {
this.deepFlattenFormControl = new FormControl(false);
this.layeringFormControl = new FormControl(false);
this.exclusiveLockFormControl = new FormControl(false);
this.objectMapFormControl = new FormControl({ value: false, disabled: true });
this.journalingFormControl = new FormControl({ value: false, disabled: true });
this.fastDiffFormControl = new FormControl({ value: false, disabled: true });
this.featuresFormGroups = new CdFormGroup({
'deep-flatten': this.deepFlattenFormControl,
layering: this.layeringFormControl,
'exclusive-lock': this.exclusiveLockFormControl,
'object-map': this.objectMapFormControl,
journaling: this.journalingFormControl,
'fast-diff': this.fastDiffFormControl
});
this.rbdForm = new CdFormGroup(
{
parent: new FormControl(''),
name: new FormControl('', {
validators: [Validators.required, Validators.pattern(/^[^@/]+?$/)]
}),
pool: new FormControl(null, {
validators: [Validators.required]
}),
useDataPool: new FormControl(false),
dataPool: new FormControl(null),
size: new FormControl(null, {
updateOn: 'blur'
}),
obj_size: new FormControl(this.defaultObjectSize),
features: this.featuresFormGroups,
stripingUnit: new FormControl(null),
stripingCount: new FormControl(null, {
updateOn: 'blur'
})
},
this.validateRbdForm(this.formatter)
);
}
disableForEdit() {
this.rbdForm.get('parent').disable();
this.rbdForm.get('pool').disable();
this.rbdForm.get('useDataPool').disable();
this.rbdForm.get('dataPool').disable();
this.rbdForm.get('obj_size').disable();
this.rbdForm.get('stripingUnit').disable();
this.rbdForm.get('stripingCount').disable();
}
disableForClone() {
this.rbdForm.get('parent').disable();
this.rbdForm.get('size').disable();
}
disableForCopy() {
this.rbdForm.get('parent').disable();
this.rbdForm.get('size').disable();
}
ngOnInit() {
if (this.router.url.startsWith('/block/rbd/edit')) {
this.mode = this.rbdFormMode.editing;
this.action = this.actionLabels.EDIT;
this.disableForEdit();
} else if (this.router.url.startsWith('/block/rbd/clone')) {
this.mode = this.rbdFormMode.cloning;
this.disableForClone();
this.action = this.actionLabels.CLONE;
} else if (this.router.url.startsWith('/block/rbd/copy')) {
this.mode = this.rbdFormMode.copying;
this.action = this.actionLabels.COPY;
this.disableForCopy();
} else {
this.action = this.actionLabels.CREATE;
}
if (
this.mode === this.rbdFormMode.editing ||
this.mode === this.rbdFormMode.cloning ||
this.mode === this.rbdFormMode.copying
) {
this.route.params.subscribe((params: { pool: string; name: string; snap: string }) => {
const poolName = decodeURIComponent(params.pool);
const rbdName = decodeURIComponent(params.name);
if (params.snap) {
this.snapName = decodeURIComponent(params.snap);
}
this.rbdService.get(poolName, rbdName).subscribe((resp: RbdFormResponseModel) => {
this.setResponse(resp, this.snapName);
});
});
} else {
this.rbdService.defaultFeatures().subscribe((defaultFeatures: Array<string>) => {
this.setFeatures(defaultFeatures);
});
}
if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
this.poolService
.list(['pool_name', 'type', 'flags_names', 'application_metadata'])
.then((resp) => {
const pools = [];
const dataPools = [];
for (const pool of resp) {
if (_.indexOf(pool.application_metadata, 'rbd') !== -1) {
if (!pool.pool_name.includes('/')) {
if (pool.type === 'replicated') {
pools.push(pool);
dataPools.push(pool);
} else if (
pool.type === 'erasure' &&
pool.flags_names.indexOf('ec_overwrites') !== -1
) {
dataPools.push(pool);
}
}
}
}
this.pools = pools;
this.allPools = pools;
this.dataPools = dataPools;
this.allDataPools = dataPools;
if (this.pools.length === 1) {
const poolName = this.pools[0]['pool_name'];
this.rbdForm.get('pool').setValue(poolName);
this.onPoolChange(poolName);
}
});
}
this.deepFlattenFormControl.valueChanges.subscribe((value) => {
this.watchDataFeatures('deep-flatten', value);
});
this.layeringFormControl.valueChanges.subscribe((value) => {
this.watchDataFeatures('layering', value);
});
this.exclusiveLockFormControl.valueChanges.subscribe((value) => {
this.watchDataFeatures('exclusive-lock', value);
});
this.objectMapFormControl.valueChanges.subscribe((value) => {
this.watchDataFeatures('object-map', value);
});
this.journalingFormControl.valueChanges.subscribe((value) => {
this.watchDataFeatures('journaling', value);
});
this.fastDiffFormControl.valueChanges.subscribe((value) => {
this.watchDataFeatures('fast-diff', value);
});
}
onPoolChange(selectedPoolName) {
const newDataPools = this.allDataPools.filter((dataPool: any) => {
return dataPool.pool_name !== selectedPoolName;
});
if (this.rbdForm.getValue('dataPool') === selectedPoolName) {
this.rbdForm.get('dataPool').setValue(null);
}
this.dataPools = newDataPools;
}
onUseDataPoolChange() {
if (!this.rbdForm.getValue('useDataPool')) {
this.rbdForm.get('dataPool').setValue(null);
this.onDataPoolChange(null);
}
}
onDataPoolChange(selectedDataPoolName) {
const newPools = this.allPools.filter((pool: any) => {
return pool.pool_name !== selectedDataPoolName;
});
if (this.rbdForm.getValue('pool') === selectedDataPoolName) {
this.rbdForm.get('pool').setValue(null);
}
this.pools = newPools;
}
validateRbdForm(formatter: FormatterService): ValidatorFn {
return (formGroup: CdFormGroup) => {
// Data Pool
const useDataPoolControl = formGroup.get('useDataPool');
const dataPoolControl = formGroup.get('dataPool');
let dataPoolControlErrors = null;
if (useDataPoolControl.value && dataPoolControl.value == null) {
dataPoolControlErrors = { required: true };
}
dataPoolControl.setErrors(dataPoolControlErrors);
// Size
const sizeControl = formGroup.get('size');
const objectSizeControl = formGroup.get('obj_size');
const objectSizeInBytes = formatter.toBytes(
objectSizeControl.value != null ? objectSizeControl.value : this.defaultObjectSize
);
const stripingCountControl = formGroup.get('stripingCount');
const stripingCount = stripingCountControl.value != null ? stripingCountControl.value : 1;
let sizeControlErrors = null;
if (sizeControl.value === null) {
sizeControlErrors = { required: true };
} else {
const sizeInBytes = formatter.toBytes(sizeControl.value);
if (stripingCount * objectSizeInBytes > sizeInBytes) {
sizeControlErrors = { invalidSizeObject: true };
}
}
sizeControl.setErrors(sizeControlErrors);
// Striping Unit
const stripingUnitControl = formGroup.get('stripingUnit');
let stripingUnitControlErrors = null;
if (stripingUnitControl.value === null && stripingCountControl.value !== null) {
stripingUnitControlErrors = { required: true };
} else if (stripingUnitControl.value !== null) {
const stripingUnitInBytes = formatter.toBytes(stripingUnitControl.value);
if (stripingUnitInBytes > objectSizeInBytes) {
stripingUnitControlErrors = { invalidStripingUnit: true };
}
}
stripingUnitControl.setErrors(stripingUnitControlErrors);
// Striping Count
let stripingCountControlErrors = null;
if (stripingCountControl.value === null && stripingUnitControl.value !== null) {
stripingCountControlErrors = { required: true };
} else if (stripingCount < 1) {
stripingCountControlErrors = { min: true };
}
stripingCountControl.setErrors(stripingCountControlErrors);
return null;
};
}
deepBoxCheck(key, checked) {
_.forIn(this.features, (details, feature) => {
if (details.requires === key) {
if (checked) {
this.rbdForm.get(feature).enable();
} else {
this.rbdForm.get(feature).disable();
this.rbdForm.get(feature).setValue(checked);
this.watchDataFeatures(feature, checked);
this.deepBoxCheck(feature, checked);
}
}
if (this.mode === this.rbdFormMode.editing && this.rbdForm.get(feature).enabled) {
if (this.response.features_name.indexOf(feature) !== -1 && !details.allowDisable) {
this.rbdForm.get(feature).disable();
} else if (this.response.features_name.indexOf(feature) === -1 && !details.allowEnable) {
this.rbdForm.get(feature).disable();
}
}
});
}
featureFormUpdate(key, checked) {
if (checked) {
const required = this.features[key].requires;
if (required && !this.rbdForm.getValue(required)) {
this.rbdForm.get(key).setValue(false);
return;
}
}
this.deepBoxCheck(key, checked);
}
watchDataFeatures(key, checked) {
this.featureFormUpdate(key, checked);
}
setFeatures(features: Array<string>) {
const featuresControl = this.rbdForm.get('features');
_.forIn(this.features, (feature) => {
if (features.indexOf(feature.key) !== -1) {
featuresControl.get(feature.key).setValue(true);
}
this.watchDataFeatures(feature.key, featuresControl.get(feature.key).value);
});
}
setResponse(response: RbdFormResponseModel, snapName: string) {
this.response = response;
if (this.mode === this.rbdFormMode.cloning) {
this.rbdForm.get('parent').setValue(`${response.pool_name}/${response.name}@${snapName}`);
} else if (this.mode === this.rbdFormMode.copying) {
if (snapName) {
this.rbdForm.get('parent').setValue(`${response.pool_name}/${response.name}@${snapName}`);
} else {
this.rbdForm.get('parent').setValue(`${response.pool_name}/${response.name}`);
}
} else if (response.parent) {
const parent = response.parent;
this.rbdForm
.get('parent')
.setValue(`${parent.pool_name}/${parent.image_name}@${parent.snap_name}`);
}
if (this.mode === this.rbdFormMode.editing) {
this.rbdForm.get('name').setValue(response.name);
}
this.rbdForm.get('pool').setValue(response.pool_name);
if (response.data_pool) {
this.rbdForm.get('useDataPool').setValue(true);
this.rbdForm.get('dataPool').setValue(response.data_pool);
}
this.rbdForm.get('size').setValue(this.dimlessBinaryPipe.transform(response.size));
this.rbdForm.get('obj_size').setValue(this.dimlessBinaryPipe.transform(response.obj_size));
this.setFeatures(response.features_name);
this.rbdForm
.get('stripingUnit')
.setValue(this.dimlessBinaryPipe.transform(response.stripe_unit));
this.rbdForm.get('stripingCount').setValue(response.stripe_count);
/* Configuration */
this.initializeConfigData.emit({
initialData: this.response.configuration,
sourceType: RbdConfigurationSourceField.image
});
}
createRequest() {
const request = new RbdFormCreateRequestModel();
request.pool_name = this.rbdForm.getValue('pool');
request.name = this.rbdForm.getValue('name');
request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
_.forIn(this.features, (feature) => {
if (this.rbdForm.getValue(feature.key)) {
request.features.push(feature.key);
}
});
/* Striping */
request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
request.stripe_count = this.rbdForm.getValue('stripingCount');
request.data_pool = this.rbdForm.getValue('dataPool');
/* Configuration */
request.configuration = this.getDirtyConfigurationValues();
return request;
}
createAction(): Observable<any> {
const request = this.createRequest();
return this.taskWrapper.wrapTaskAroundCall({
task: new FinishedTask('rbd/create', {
pool_name: request.pool_name,
image_name: request.name
}),
call: this.rbdService.create(request)
});
}
editRequest() {
const request = new RbdFormEditRequestModel();
request.name = this.rbdForm.getValue('name');
request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
_.forIn(this.features, (feature) => {
if (this.rbdForm.getValue(feature.key)) {
request.features.push(feature.key);
}
});
request.configuration = this.getDirtyConfigurationValues();
return request;
}
cloneRequest(): RbdFormCloneRequestModel {
const request = new RbdFormCloneRequestModel();
request.child_pool_name = this.rbdForm.getValue('pool');
request.child_image_name = this.rbdForm.getValue('name');
request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
_.forIn(this.features, (feature) => {
if (this.rbdForm.getValue(feature.key)) {
request.features.push(feature.key);
}
});
/* Striping */
request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
request.stripe_count = this.rbdForm.getValue('stripingCount');
request.data_pool = this.rbdForm.getValue('dataPool');
/* Configuration */
request.configuration = this.getDirtyConfigurationValues(
true,
RbdConfigurationSourceField.image
);
return request;
}
editAction(): Observable<any> {
return this.taskWrapper.wrapTaskAroundCall({
task: new FinishedTask('rbd/edit', {
pool_name: this.response.pool_name,
image_name: this.response.name
}),
call: this.rbdService.update(this.response.pool_name, this.response.name, this.editRequest())
});
}
cloneAction(): Observable<any> {
const request = this.cloneRequest();
return this.taskWrapper.wrapTaskAroundCall({
task: new FinishedTask('rbd/clone', {
parent_pool_name: this.response.pool_name,
parent_image_name: this.response.name,
parent_snap_name: this.snapName,
child_pool_name: request.child_pool_name,
child_image_name: request.child_image_name
}),
call: this.rbdService.cloneSnapshot(
this.response.pool_name,
this.response.name,
this.snapName,
request
)
});
}
copyRequest(): RbdFormCopyRequestModel {
const request = new RbdFormCopyRequestModel();
if (this.snapName) {
request.snapshot_name = this.snapName;
}
request.dest_pool_name = this.rbdForm.getValue('pool');
request.dest_image_name = this.rbdForm.getValue('name');
request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
_.forIn(this.features, (feature) => {
if (this.rbdForm.getValue(feature.key)) {
request.features.push(feature.key);
}
});
/* Striping */
request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
request.stripe_count = this.rbdForm.getValue('stripingCount');
request.data_pool = this.rbdForm.getValue('dataPool');
/* Configuration */
request.configuration = this.getDirtyConfigurationValues(
true,
RbdConfigurationSourceField.image
);
return request;
}
copyAction(): Observable<any> {
const request = this.copyRequest();
return this.taskWrapper.wrapTaskAroundCall({
task: new FinishedTask('rbd/copy', {
src_pool_name: this.response.pool_name,
src_image_name: this.response.name,
dest_pool_name: request.dest_pool_name,
dest_image_name: request.dest_image_name
}),
call: this.rbdService.copy(this.response.pool_name, this.response.name, request)
});
}
submit() {
let action: Observable<any>;
if (this.mode === this.rbdFormMode.editing) {
action = this.editAction();
} else if (this.mode === this.rbdFormMode.cloning) {
action = this.cloneAction();
} else if (this.mode === this.rbdFormMode.copying) {
action = this.copyAction();
} else {
action = this.createAction();
}
action.subscribe(
undefined,
() => this.rbdForm.setErrors({ cdSubmitButton: true }),
() => this.router.navigate(['/block/rbd'])
);
}
}
<div class="col-sm-12 col-lg-6">
<form name="rbdForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="rbdForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 i18n="form title|Example: Create Pool@@formTitle"
class="panel-title">{{ action | titlecase }} {{ resource | upperFirst }}</h3>
</div>
<div class="panel-body">
<!-- Parent -->
<div class="form-group"
*ngIf="rbdForm.getValue('parent')">
<label i18n
class="control-label col-sm-3"
for="name">{{ action | titlecase }} from</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
id="parent"
name="parent"
formControlName="parent">
<hr>
</div>
</div>
<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('name', formDir)}">
<label class="control-label col-sm-3"
for="name">
<ng-container i18n>Name</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
placeholder="Name..."
id="name"
name="name"
formControlName="name"
autofocus>
<span class="help-block"
*ngIf="rbdForm.showError('name', formDir, 'required')">
<ng-container i18n>This field is required.</ng-container>
</span>
<span class="help-block"
*ngIf="rbdForm.showError('name', formDir, 'pattern')">
<ng-container i18n>'/' and '@' are not allowed.</ng-container>
</span>
</div>
</div>
<!-- Pool -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('pool', formDir)}"
(change)="onPoolChange($event.target.value)">
<label class="control-label col-sm-3"
for="pool">
Pool
<span class="required"
*ngIf="mode !== 'editing'"></span>
</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
placeholder="Pool name..."
id="pool"
name="pool"
formControlName="pool"
*ngIf="mode === 'editing' || !poolPermission.read">
<select id="pool"
name="pool"
class="form-control"
formControlName="pool"
*ngIf="mode !== 'editing' && poolPermission.read">
<option *ngIf="pools === null"
[ngValue]="null"
i18n>Loading...</option>
<option *ngIf="pools !== null && pools.length === 0"
[ngValue]="null"
i18n>-- No rbd pools available --</option>
<option *ngIf="pools !== null && pools.length > 0"
[ngValue]="null"
i18n>-- Select a pool --</option>
<option *ngFor="let pool of pools"
[value]="pool.pool_name">{{ pool.pool_name }}</option>
</select>
<span *ngIf="rbdForm.showError('pool', formDir, 'required')"
class="help-block"
i18n>This field is required.</span>
</div>
</div>
<!-- Use a dedicated pool -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox checkbox-primary">
<input type="checkbox"
id="useDataPool"
name="useDataPool"
formControlName="useDataPool"
(change)="onUseDataPoolChange()">
<label i18n
for="useDataPool">Use a dedicated data pool</label>
</div>
</div>
</div>
<!-- Data Pool -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('dataPool', formDir)}"
*ngIf="rbdForm.getValue('useDataPool')">
<label class="control-label col-sm-3"
for="dataPool">
<ng-container i18n>Data pool</ng-container>
<span class="required"
*ngIf="mode !== 'editing'"></span>
<cd-helper i18n-html
html="Dedicated pool that stores the object-data of the RBD.">
</cd-helper>
</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
placeholder="Data pool name..."
id="dataPool"
name="dataPool"
formControlName="dataPool"
*ngIf="mode === 'editing' || !poolPermission.read">
<select id="dataPool"
name="dataPool"
class="form-control"
formControlName="dataPool"
(change)="onDataPoolChange($event.target.value)"
*ngIf="mode !== 'editing' && poolPermission.read">
<option *ngIf="dataPools === null"
[ngValue]="null"
i18n>Loading...</option>
<option *ngIf="dataPools !== null && dataPools.length === 0"
[ngValue]="null"
i18n>-- No data pools available --</option>
<option *ngIf="dataPools !== null && dataPools.length > 0"
[ngValue]="null">-- Select a data pool --
</option>
<option *ngFor="let dataPool of dataPools"
[value]="dataPool.pool_name">{{ dataPool.pool_name }}</option>
</select>
<span class="help-block"
*ngIf="rbdForm.showError('dataPool', formDir, 'required')"
i18n>This field is required.</span>
</div>
</div>
<!-- Size -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('size', formDir)}">
<label class="control-label col-sm-3"
for="size">
<ng-container i18n>Size</ng-container>
<span class="required"></span>
</label>
<div class="col-sm-9">
<input id="size"
name="size"
class="form-control"
type="text"
formControlName="size"
i18n-placeholder
placeholder="e.g., 10GiB"
defaultUnit="GiB"
cdDimlessBinary>
<span class="help-block"
*ngIf="rbdForm.showError('size', formDir, 'required')"
i18n>This field is required.</span>
<span class="help-block"
*ngIf="rbdForm.showError('size', formDir, 'invalidSizeObject')"
i18n>You have to increase the size.</span>
</div>
</div>
<!-- Features -->
<div class="form-group"
[ngClass]="{'has-error': (formDir.submitted || featuresFormGroups.dirty) && featuresFormGroups.invalid}"
formGroupName="features">
<label i18n
class="col-sm-3 control-label"
for="features">Features</label>
<div class="col-sm-9">
<div class="checkbox checkbox-primary"
*ngFor="let feature of featuresList">
<input type="checkbox"
id="{{ feature.key }}"
name="{{ feature.key }}"
formControlName="{{ feature.key }}">
<label for="{{ feature.key }}">{{ feature.desc }}</label>
<cd-helper *ngIf="feature.helperHtml"
html="{{ feature.helperHtml }}">
</cd-helper>
</div>
</div>
</div>
<!-- Advanced -->
<div class="row">
<div class="col-sm-12">
<a class="pull-right margin-right-md"
(click)="advancedEnabled = true"
*ngIf="!advancedEnabled"
i18n>Advanced...</a>
</div>
</div>
<div [hidden]="!advancedEnabled">
<h2 i18n
class="page-header">Advanced</h2>
<div class="section">
<h3 class="page-header" i18n>Striping</h3>
<!-- Object Size -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('obj_size', formDir)}">
<label i18n
class="control-label col-sm-3"
for="size">Object size</label>
<div class="col-sm-9">
<select id="obj_size"
name="obj_size"
class="form-control"
formControlName="obj_size">
<option *ngFor="let objectSize of objectSizes"
[value]="objectSize">{{ objectSize }}</option>
</select>
</div>
</div>
<!-- Stripe Unit -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('stripingUnit', formDir)}">
<label class="control-label col-sm-3"
for="stripingUnit">
<span i18n>Stripe unit</span>
<span class="required"
*ngIf="rbdForm.getValue('stripingCount')">
</span>
</label>
<div class="col-sm-9">
<select id="stripingUnit"
name="stripingUnit"
class="form-control"
formControlName="stripingUnit">
<option i18n
[ngValue]="null">-- Select stripe unit --</option>
<option *ngFor="let objectSize of objectSizes"
[value]="objectSize">{{ objectSize }}</option>
</select>
<span class="help-block"
*ngIf="rbdForm.showError('stripingUnit', formDir, 'required')"
i18n>This field is required because stripe count is defined!</span>
<span class="help-block"
*ngIf="rbdForm.showError('stripingUnit', formDir, 'invalidStripingUnit')"
i18n>Stripe unit is greater than object size.</span>
</div>
</div>
<!-- Stripe Count -->
<div class="form-group"
[ngClass]="{'has-error': rbdForm.showError('stripingCount', formDir)}">
<label class="control-label col-sm-3"
for="stripingCount">
<span i18n>Stripe count</span>
<span class="required"
*ngIf="rbdForm.getValue('stripingUnit')">
</span>
</label>
<div class="col-sm-9">
<input id="stripingCount"
name="stripingCount"
formControlName="stripingCount"
class="form-control"
type="number">
<span class="help-block"
*ngIf="rbdForm.showError('stripingCount', formDir, 'required')"
i18n>This field is required because stripe unit is defined!</span>
<span class="help-block"
*ngIf="rbdForm.showError('stripingCount', formDir, 'min')"
i18n>Stripe count must be greater than 0.</span>
</div>
</div>
</div>
<div class="section">
<cd-rbd-configuration-form [form]="rbdForm"
[initializeData]="initializeConfigData"
(changes)="getDirtyConfigurationValues = $event"></cd-rbd-configuration-form>
</div>
</div>
</div>
<div class="panel-footer">
<div class="button-group text-right">
<cd-submit-button
[form]="formDir"
(submitAction)="submit()"
i18n="form action button|Example: Create Pool@@formActionButton"
type="button">{{ action | titlecase }} {{ resource | upperFirst }}</cd-submit-button>
<cd-back-button></cd-back-button>
</div>
</div>
</div>
</form>
</div>