File

src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts

Implements

OnInit

Metadata

selector cd-iscsi-target-form
styleUrls ./iscsi-target-form.component.scss
templateUrl ./iscsi-target-form.component.html

Index

Properties
Methods
Accessors

Constructor

constructor(iscsiService: IscsiService, modalService: BsModalService, rbdService: RbdService, router: Router, route: ActivatedRoute, i18n: I18n, taskWrapper: TaskWrapperService, actionLabels: ActionLabelsI18n)
Parameters :
Name Type Optional
iscsiService IscsiService No
modalService BsModalService No
rbdService RbdService No
router Router No
route ActivatedRoute No
i18n I18n No
taskWrapper TaskWrapperService No
actionLabels ActionLabelsI18n No

Methods

addGroup
addGroup()
Returns : any
addInitiator
addInitiator()
Returns : any
createForm
createForm()
Returns : void
getDefaultBackstore
getDefaultBackstore(imageId)
Parameters :
Name Optional
imageId No
Returns : string
getImageById
getImageById(imageId)
Parameters :
Name Optional
imageId No
Returns : any
getValidBackstores
getValidBackstores(image)
Parameters :
Name Optional
image No
Returns : any
hasAdvancedSettings
hasAdvancedSettings(settings: any)
Parameters :
Name Type Optional
settings any No
Returns : boolean
imageSettingsModal
imageSettingsModal(image)
Parameters :
Name Optional
image No
Returns : void
ngOnInit
ngOnInit()
Returns : void
onGroupMemberSelection
onGroupMemberSelection($event)
Parameters :
Name Optional
$event No
Returns : void
onImageSelection
onImageSelection($event)
Parameters :
Name Optional
$event No
Returns : void
onPortalSelection
onPortalSelection()
Returns : void
removeGroup
removeGroup(index)
Parameters :
Name Optional
index No
Returns : void
removeGroupDisk
removeGroupDisk(group, disk_index, group_index)
Parameters :
Name Optional
group No
disk_index No
group_index No
Returns : void
removeGroupInitiator
removeGroupInitiator(group, member_index, group_index)
Parameters :
Name Optional
group No
member_index No
group_index No
Returns : void
removeImage
removeImage(index: number, image: string)
Parameters :
Name Type Optional
index number No
image string No
Returns : boolean
removeImageRefs
removeImageRefs(name)
Parameters :
Name Optional
name No
Returns : void
removeInitiator
removeInitiator(index)
Parameters :
Name Optional
index No
Returns : void
removeInitiatorImage
removeInitiatorImage(initiator: any, lun_index: number, initiator_index: string, image: string)
Parameters :
Name Type Optional
initiator any No
lun_index number No
initiator_index string No
image string No
Returns : boolean
removePortal
removePortal(index: number, portal: string)
Parameters :
Name Type Optional
index number No
portal string No
Returns : boolean
resolveModel
resolveModel(res)
Parameters :
Name Optional
res No
Returns : void
submit
submit()
Returns : void
targetSettingsModal
targetSettingsModal()
Returns : void
updatedInitiatorSelector
updatedInitiatorSelector()
Returns : void
validFeatures
validFeatures(image, backstore)
Parameters :
Name Optional
image No
backstore No
Returns : boolean

Properties

action
Type : string
Public actionLabels
Type : ActionLabelsI18n
backstores
Type : string[]
default_backstore
Type : string
disk_default_controls
Type : any
groupDiskSelections
Type : SelectOption[][]
Default value : []
groupMembersSelections
Type : SelectOption[][]
Default value : []
imagesAll
Type : any[]
imagesInitiatorSelections
Type : SelectOption[][]
Default value : []
imagesSelections
Type : SelectOption[]
imagesSettings
Type : any
Default value : {}
IQN_REGEX
Default value : /^iqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+)*$/
isEdit
Default value : false
messages
Type : object
Default value : { portals: new SelectMessages( { noOptions: this.i18n('There are no portals available.') }, this.i18n ), images: new SelectMessages( { noOptions: this.i18n('There are no images available.') }, this.i18n ), initiatorImage: new SelectMessages( { noOptions: this.i18n( 'There are no images available. Please make sure you add an image to the target.' ) }, this.i18n ), groupInitiator: new SelectMessages( { noOptions: this.i18n( 'There are no initiators available. Please make sure you add an initiator to the target.' ) }, this.i18n ) }
minimum_gateways
Type : number
Default value : 1
modalRef
Type : BsModalRef
PASSWORD_REGEX
Default value : /[\w@\-_\/]{12,16}/
portalsSelections
Type : SelectOption[]
Default value : []
required_rbd_features
Type : any
resource
Type : string
supported_rbd_features
Type : any
target_default_controls
Type : any
target_iqn
Type : string
targetForm
Type : CdFormGroup
USER_REGEX
Default value : /[\w\.:@_-]{8,64}/

Accessors

portals
getportals()
disks
getdisks()
initiators
getinitiators()
groups
getgroups()
import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';

import { I18n } from '@ngx-translate/i18n-polyfill';
import * as _ from 'lodash';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { forkJoin } from 'rxjs';

import { IscsiService } from '../../../shared/api/iscsi.service';
import { RbdService } from '../../../shared/api/rbd.service';
import { SelectMessages } from '../../../shared/components/select/select-messages.model';
import { SelectOption } from '../../../shared/components/select/select-option.model';
import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
import { CdFormGroup } from '../../../shared/forms/cd-form-group';
import { CdValidators } from '../../../shared/forms/cd-validators';
import { FinishedTask } from '../../../shared/models/finished-task';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { IscsiTargetImageSettingsModalComponent } from '../iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component';
import { IscsiTargetIqnSettingsModalComponent } from '../iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component';

@Component({
  selector: 'cd-iscsi-target-form',
  templateUrl: './iscsi-target-form.component.html',
  styleUrls: ['./iscsi-target-form.component.scss']
})
export class IscsiTargetFormComponent implements OnInit {
  targetForm: CdFormGroup;
  modalRef: BsModalRef;
  minimum_gateways = 1;
  target_default_controls: any;
  disk_default_controls: any;
  backstores: string[];
  default_backstore: string;
  supported_rbd_features: any;
  required_rbd_features: any;

  isEdit = false;
  target_iqn: string;

  imagesAll: any[];
  imagesSelections: SelectOption[];
  portalsSelections: SelectOption[] = [];

  imagesInitiatorSelections: SelectOption[][] = [];
  groupDiskSelections: SelectOption[][] = [];
  groupMembersSelections: SelectOption[][] = [];

  imagesSettings: any = {};
  messages = {
    portals: new SelectMessages(
      { noOptions: this.i18n('There are no portals available.') },
      this.i18n
    ),
    images: new SelectMessages(
      { noOptions: this.i18n('There are no images available.') },
      this.i18n
    ),
    initiatorImage: new SelectMessages(
      {
        noOptions: this.i18n(
          'There are no images available. Please make sure you add an image to the target.'
        )
      },
      this.i18n
    ),
    groupInitiator: new SelectMessages(
      {
        noOptions: this.i18n(
          'There are no initiators available. Please make sure you add an initiator to the target.'
        )
      },
      this.i18n
    )
  };

  IQN_REGEX = /^iqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+)*$/;
  USER_REGEX = /[\w\.:@_-]{8,64}/;
  PASSWORD_REGEX = /[\w@\-_\/]{12,16}/;
  action: string;
  resource: string;

  constructor(
    private iscsiService: IscsiService,
    private modalService: BsModalService,
    private rbdService: RbdService,
    private router: Router,
    private route: ActivatedRoute,
    private i18n: I18n,
    private taskWrapper: TaskWrapperService,
    public actionLabels: ActionLabelsI18n
  ) {
    this.resource = this.i18n('target');
  }

  ngOnInit() {
    const promises: any[] = [
      this.iscsiService.listTargets(),
      this.rbdService.list(),
      this.iscsiService.portals(),
      this.iscsiService.settings()
    ];

    if (this.router.url.startsWith('/block/iscsi/targets/edit')) {
      this.isEdit = true;
      this.route.params.subscribe((params: { target_iqn: string }) => {
        this.target_iqn = decodeURIComponent(params.target_iqn);
        promises.push(this.iscsiService.getTarget(this.target_iqn));
      });
    }
    this.action = this.isEdit ? this.actionLabels.EDIT : this.actionLabels.CREATE;

    forkJoin(promises).subscribe((data: any[]) => {
      // iscsiService.listTargets
      const usedImages = _(data[0])
        .filter((target) => target.target_iqn !== this.target_iqn)
        .flatMap((target) => target.disks)
        .map((image) => `${image.pool}/${image.image}`)
        .value();

      // iscsiService.settings()
      this.minimum_gateways = data[3].config.minimum_gateways;
      this.target_default_controls = data[3].target_default_controls;
      this.disk_default_controls = data[3].disk_default_controls;
      this.backstores = data[3].backstores;
      this.default_backstore = data[3].default_backstore;
      this.supported_rbd_features = data[3].supported_rbd_features;
      this.required_rbd_features = data[3].required_rbd_features;

      // rbdService.list()
      this.imagesAll = _(data[1])
        .flatMap((pool) => pool.value)
        .filter((image) => {
          const imageId = `${image.pool_name}/${image.name}`;
          if (usedImages.indexOf(imageId) !== -1) {
            return false;
          }
          const validBackstores = this.getValidBackstores(image);
          if (validBackstores.length === 0) {
            return false;
          }
          return true;
        })
        .value();

      this.imagesSelections = this.imagesAll.map(
        (image) => new SelectOption(false, `${image.pool_name}/${image.name}`, '')
      );

      // iscsiService.portals()
      const portals: SelectOption[] = [];
      data[2].forEach((portal) => {
        portal.ip_addresses.forEach((ip) => {
          portals.push(new SelectOption(false, portal.name + ':' + ip, ''));
        });
      });
      this.portalsSelections = [...portals];

      this.createForm();

      // iscsiService.getTarget()
      if (data[4]) {
        this.resolveModel(data[4]);
      }
    });
  }

  createForm() {
    this.targetForm = new CdFormGroup({
      target_iqn: new FormControl('iqn.2001-07.com.ceph:' + Date.now(), {
        validators: [Validators.required, Validators.pattern(this.IQN_REGEX)]
      }),
      target_controls: new FormControl({}),
      portals: new FormControl([], {
        validators: [
          CdValidators.custom('minGateways', (value) => {
            const gateways = _.uniq(value.map((elem) => elem.split(':')[0]));
            return gateways.length < Math.max(1, this.minimum_gateways);
          })
        ]
      }),
      disks: new FormControl([]),
      initiators: new FormArray([]),
      groups: new FormArray([]),
      acl_enabled: new FormControl(false)
    });
  }

  resolveModel(res) {
    this.targetForm.patchValue({
      target_iqn: res.target_iqn,
      target_controls: res.target_controls,
      acl_enabled: res.acl_enabled
    });

    const portals = [];
    _.forEach(res.portals, (portal) => {
      const id = `${portal.host}:${portal.ip}`;
      portals.push(id);
    });
    this.targetForm.patchValue({
      portals: portals
    });

    const disks = [];
    _.forEach(res.disks, (disk) => {
      const id = `${disk.pool}/${disk.image}`;
      disks.push(id);
      this.imagesSettings[id] = {
        backstore: disk.backstore
      };
      this.imagesSettings[id][disk.backstore] = disk.controls;

      this.onImageSelection({ option: { name: id, selected: true } });
    });
    this.targetForm.patchValue({
      disks: disks
    });

    _.forEach(res.clients, (client) => {
      const initiator = this.addInitiator();
      client.luns = _.map(client.luns, (lun) => `${lun.pool}/${lun.image}`);
      initiator.patchValue(client);
      // updatedInitiatorSelector()
    });

    _.forEach(res.groups, (group) => {
      const fg = this.addGroup();
      console.log(group);
      group.disks = _.map(group.disks, (disk) => `${disk.pool}/${disk.image}`);
      fg.patchValue(group);
      _.forEach(group.members, (member) => {
        this.onGroupMemberSelection({ option: new SelectOption(true, member, '') });
      });
    });
  }

  hasAdvancedSettings(settings: any) {
    return Object.values(settings).length > 0;
  }

  // Portals
  get portals() {
    return this.targetForm.get('portals') as FormControl;
  }

  onPortalSelection() {
    this.portals.setValue(this.portals.value);
  }

  removePortal(index: number, portal: string) {
    this.portalsSelections.forEach((value) => {
      if (value.name === portal) {
        value.selected = false;
      }
    });

    this.portals.value.splice(index, 1);
    this.portals.setValue(this.portals.value);
    return false;
  }

  // Images
  get disks() {
    return this.targetForm.get('disks') as FormControl;
  }

  removeImage(index: number, image: string) {
    this.imagesSelections.forEach((value) => {
      if (value.name === image) {
        value.selected = false;
      }
    });
    this.disks.value.splice(index, 1);
    this.removeImageRefs(image);
    return false;
  }

  removeImageRefs(name) {
    this.initiators.controls.forEach((element) => {
      const newImages = element.value.luns.filter((item) => item !== name);
      element.get('luns').setValue(newImages);
    });

    this.groups.controls.forEach((element) => {
      const newDisks = element.value.disks.filter((item) => item !== name);
      element.get('disks').setValue(newDisks);
    });

    _.forEach(this.imagesInitiatorSelections, (selections, i) => {
      this.imagesInitiatorSelections[i] = selections.filter((item: any) => item.name !== name);
    });
    _.forEach(this.groupDiskSelections, (selections, i) => {
      this.groupDiskSelections[i] = selections.filter((item: any) => item.name !== name);
    });
  }

  getDefaultBackstore(imageId) {
    let result = this.default_backstore;
    const image = this.getImageById(imageId);
    if (!this.validFeatures(image, this.default_backstore)) {
      this.backstores.forEach((backstore) => {
        if (backstore !== this.default_backstore) {
          if (this.validFeatures(image, backstore)) {
            result = backstore;
          }
        }
      });
    }
    return result;
  }

  onImageSelection($event) {
    const option = $event.option;

    if (option.selected) {
      if (!this.imagesSettings[option.name]) {
        const defaultBackstore = this.getDefaultBackstore(option.name);
        this.imagesSettings[option.name] = {
          backstore: defaultBackstore
        };
        this.imagesSettings[option.name][defaultBackstore] = {};
      }

      _.forEach(this.imagesInitiatorSelections, (selections, i) => {
        selections.push(new SelectOption(false, option.name, ''));
        this.imagesInitiatorSelections[i] = [...selections];
      });

      _.forEach(this.groupDiskSelections, (selections, i) => {
        selections.push(new SelectOption(false, option.name, ''));
        this.groupDiskSelections[i] = [...selections];
      });
    } else {
      this.removeImageRefs(option.name);
    }
  }

  // Initiators
  get initiators() {
    return this.targetForm.get('initiators') as FormArray;
  }

  addInitiator() {
    const fg = new CdFormGroup({
      client_iqn: new FormControl('', {
        validators: [
          Validators.required,
          CdValidators.custom('notUnique', (client_iqn) => {
            const flattened = this.initiators.controls.reduce(function(accumulator, currentValue) {
              return accumulator.concat(currentValue.value.client_iqn);
            }, []);

            return flattened.indexOf(client_iqn) !== flattened.lastIndexOf(client_iqn);
          }),
          Validators.pattern(this.IQN_REGEX)
        ]
      }),
      auth: new CdFormGroup({
        user: new FormControl(''),
        password: new FormControl(''),
        mutual_user: new FormControl(''),
        mutual_password: new FormControl('')
      }),
      luns: new FormControl([]),
      cdIsInGroup: new FormControl(false)
    });

    CdValidators.validateIf(
      fg.get('user'),
      () => fg.getValue('password') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
      [Validators.required],
      [Validators.pattern(this.USER_REGEX)],
      [fg.get('password'), fg.get('mutual_user'), fg.get('mutual_password')]
    );

    CdValidators.validateIf(
      fg.get('password'),
      () => fg.getValue('user') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
      [Validators.required],
      [Validators.pattern(this.PASSWORD_REGEX)],
      [fg.get('user'), fg.get('mutual_user'), fg.get('mutual_password')]
    );

    CdValidators.validateIf(
      fg.get('mutual_user'),
      () => fg.getValue('mutual_password'),
      [Validators.required],
      [Validators.pattern(this.USER_REGEX)],
      [fg.get('user'), fg.get('password'), fg.get('mutual_password')]
    );

    CdValidators.validateIf(
      fg.get('mutual_password'),
      () => fg.getValue('mutual_user'),
      [Validators.required],
      [Validators.pattern(this.PASSWORD_REGEX)],
      [fg.get('user'), fg.get('password'), fg.get('mutual_user')]
    );

    this.initiators.push(fg);

    _.forEach(this.groupMembersSelections, (selections, i) => {
      selections.push(new SelectOption(false, '', ''));
      this.groupMembersSelections[i] = [...selections];
    });

    const disks = _.map(
      this.targetForm.getValue('disks'),
      (disk) => new SelectOption(false, disk, '')
    );
    this.imagesInitiatorSelections.push(disks);

    return fg;
  }

  removeInitiator(index) {
    const removed = this.initiators.value[index];

    this.initiators.removeAt(index);

    _.forEach(this.groupMembersSelections, (selections, i) => {
      selections.splice(index, 1);
      this.groupMembersSelections[i] = [...selections];
    });

    this.groups.controls.forEach((element) => {
      const newMembers = element.value.members.filter((item) => item !== removed.client_iqn);
      element.get('members').setValue(newMembers);
    });

    this.imagesInitiatorSelections.splice(index, 1);
  }

  updatedInitiatorSelector() {
    // Validate all client_iqn
    this.initiators.controls.forEach((control) => {
      control.get('client_iqn').updateValueAndValidity({ emitEvent: false });
    });

    // Update Group Initiator Selector
    _.forEach(this.groupMembersSelections, (group, group_index) => {
      _.forEach(group, (elem, index) => {
        const oldName = elem.name;
        elem.name = this.initiators.controls[index].value.client_iqn;

        this.groups.controls.forEach((element) => {
          const members = element.value.members;
          const i = members.indexOf(oldName);

          if (i !== -1) {
            members[i] = elem.name;
          }
          element.get('members').setValue(members);
        });
      });
      this.groupMembersSelections[group_index] = [...this.groupMembersSelections[group_index]];
    });
  }

  removeInitiatorImage(initiator: any, lun_index: number, initiator_index: string, image: string) {
    const luns = initiator.getValue('luns');
    luns.splice(lun_index, 1);
    initiator.patchValue({ luns: luns });

    this.imagesInitiatorSelections[initiator_index].forEach((value) => {
      if (value.name === image) {
        value.selected = false;
      }
    });

    return false;
  }

  // Groups
  get groups() {
    return this.targetForm.get('groups') as FormArray;
  }

  addGroup() {
    const fg = new CdFormGroup({
      group_id: new FormControl('', { validators: [Validators.required] }),
      members: new FormControl([]),
      disks: new FormControl([])
    });

    this.groups.push(fg);

    const disks = _.map(
      this.targetForm.getValue('disks'),
      (disk) => new SelectOption(false, disk, '')
    );
    this.groupDiskSelections.push(disks);

    const initiators = _.map(
      this.initiators.value,
      (initiator) => new SelectOption(false, initiator.client_iqn, '')
    );
    this.groupMembersSelections.push(initiators);

    return fg;
  }

  removeGroup(index) {
    this.groups.removeAt(index);
    this.groupDiskSelections.splice(index, 1);
  }

  onGroupMemberSelection($event) {
    const option = $event.option;

    this.initiators.controls.forEach((element) => {
      if (element.value.client_iqn === option.name) {
        element.patchValue({ luns: [] });
        element.get('cdIsInGroup').setValue(option.selected);
      }
    });
  }

  removeGroupInitiator(group, member_index, group_index) {
    const name = group.getValue('members')[member_index];
    group.getValue('members').splice(member_index, 1);

    this.groupMembersSelections[group_index].forEach((value) => {
      if (value.name === name) {
        value.selected = false;
      }
    });
    this.groupMembersSelections[group_index] = [...this.groupMembersSelections[group_index]];

    this.onGroupMemberSelection({ option: new SelectOption(false, name, '') });
  }

  removeGroupDisk(group, disk_index, group_index) {
    const name = group.getValue('disks')[disk_index];
    group.getValue('disks').splice(disk_index, 1);

    this.groupDiskSelections[group_index].forEach((value) => {
      if (value.name === name) {
        value.selected = false;
      }
    });
    this.groupDiskSelections[group_index] = [...this.groupDiskSelections[group_index]];
  }

  submit() {
    const formValue = this.targetForm.value;

    const request = {
      target_iqn: this.targetForm.getValue('target_iqn'),
      target_controls: this.targetForm.getValue('target_controls'),
      acl_enabled: this.targetForm.getValue('acl_enabled'),
      portals: [],
      disks: [],
      clients: [],
      groups: []
    };

    // Disks
    formValue.disks.forEach((disk) => {
      const imageSplit = disk.split('/');
      const backstore = this.imagesSettings[disk].backstore;
      request.disks.push({
        pool: imageSplit[0],
        image: imageSplit[1],
        backstore: backstore,
        controls: this.imagesSettings[disk][backstore]
      });
    });

    // Portals
    formValue.portals.forEach((portal) => {
      const portalSplit = portal.split(':');
      request.portals.push({
        host: portalSplit[0],
        ip: portalSplit[1]
      });
    });

    // Clients
    if (request.acl_enabled) {
      formValue.initiators.forEach((initiator) => {
        if (!initiator.auth.user) {
          initiator.auth.user = null;
        }
        if (!initiator.auth.password) {
          initiator.auth.password = null;
        }
        if (!initiator.auth.mutual_user) {
          initiator.auth.mutual_user = null;
        }
        if (!initiator.auth.mutual_password) {
          initiator.auth.mutual_password = null;
        }

        const newLuns = [];
        initiator.luns.forEach((lun) => {
          const imageSplit = lun.split('/');
          newLuns.push({
            pool: imageSplit[0],
            image: imageSplit[1]
          });
        });

        initiator.luns = newLuns;
      });
      request.clients = formValue.initiators;
    }

    // Groups
    if (request.acl_enabled) {
      formValue.groups.forEach((group) => {
        const newDisks = [];
        group.disks.forEach((disk) => {
          const imageSplit = disk.split('/');
          newDisks.push({
            pool: imageSplit[0],
            image: imageSplit[1]
          });
        });

        group.disks = newDisks;
      });
      request.groups = formValue.groups;
    }

    let wrapTask;
    if (this.isEdit) {
      request['new_target_iqn'] = request.target_iqn;
      request.target_iqn = this.target_iqn;
      wrapTask = this.taskWrapper.wrapTaskAroundCall({
        task: new FinishedTask('iscsi/target/edit', {
          target_iqn: request.target_iqn
        }),
        call: this.iscsiService.updateTarget(this.target_iqn, request)
      });
    } else {
      wrapTask = this.taskWrapper.wrapTaskAroundCall({
        task: new FinishedTask('iscsi/target/create', {
          target_iqn: request.target_iqn
        }),
        call: this.iscsiService.createTarget(request)
      });
    }

    wrapTask.subscribe(
      undefined,
      () => {
        this.targetForm.setErrors({ cdSubmitButton: true });
      },
      () => this.router.navigate(['/block/iscsi/targets'])
    );
  }

  targetSettingsModal() {
    const initialState = {
      target_controls: this.targetForm.get('target_controls'),
      target_default_controls: this.target_default_controls
    };

    this.modalRef = this.modalService.show(IscsiTargetIqnSettingsModalComponent, { initialState });
  }

  imageSettingsModal(image) {
    const initialState = {
      imagesSettings: this.imagesSettings,
      image: image,
      disk_default_controls: this.disk_default_controls,
      backstores: this.getValidBackstores(this.getImageById(image))
    };

    this.modalRef = this.modalService.show(IscsiTargetImageSettingsModalComponent, {
      initialState
    });
  }

  validFeatures(image, backstore) {
    const imageFeatures = image.features;
    const requiredFeatures = this.required_rbd_features[backstore];
    const supportedFeatures = this.supported_rbd_features[backstore];
    // tslint:disable-next-line:no-bitwise
    const validRequiredFeatures = (imageFeatures & requiredFeatures) === requiredFeatures;
    // tslint:disable-next-line:no-bitwise
    const validSupportedFeatures = (imageFeatures & supportedFeatures) === imageFeatures;
    return validRequiredFeatures && validSupportedFeatures;
  }

  getImageById(imageId) {
    return this.imagesAll.find((image) => imageId === `${image.pool_name}/${image.name}`);
  }

  getValidBackstores(image) {
    return this.backstores.filter((backstore) => this.validFeatures(image, backstore));
  }
}
<div class="col-sm-12 col-lg-6">
  <form name="targetForm"
        class="form-horizontal"
        #formDir="ngForm"
        [formGroup]="targetForm"
        novalidate
        *ngIf="targetForm">
    <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">
        <!-- Target IQN -->
        <div class="form-group"
             [ngClass]="{'has-error': targetForm.showError('target_iqn', formDir)}">
          <label class="control-label col-sm-3"
                 for="target_iqn">
            <ng-container i18n>Target IQN</ng-container>
            <span class="required"></span>
          </label>
          <div class="col-sm-9">
            <div class="input-group">
              <input class="form-control"
                     type="text"
                     id="target_iqn"
                     name="target_iqn"
                     formControlName="target_iqn" />
              <span class="input-group-btn">
                <button class="btn btn-default"
                        id="ecp-info-button"
                        type="button"
                        (click)="targetSettingsModal()">
                  <i class="fa fa-cogs fa-fw"
                     aria-hidden="true"></i>
                </button>
              </span>
            </div>

            <span class="help-block"
                  *ngIf="targetForm.showError('target_iqn', formDir, 'required')"
                  i18n>This field is required.</span>

            <span class="help-block"
                  *ngIf="targetForm.showError('target_iqn', formDir, 'pattern')"
                  i18n>IQN has wrong pattern.</span>

            <span class="help-block"
                  *ngIf="targetForm.showError('target_iqn', formDir, 'iqn')">
              <ng-container i18n>An IQN has the following notation 'iqn.$year-$month.$reversedAddress:$definedName'</ng-container>
              <br>
              <ng-container i18n>For example: iqn.2016-06.org.dashboard:storage:disk.sn-a8675309</ng-container>
              <br>
              <a target="_blank"
                 href="https://en.wikipedia.org/wiki/ISCSI#Addressing"
                 i18n>More information</a>
            </span>

            <span class="help-block"
                  *ngIf="hasAdvancedSettings(targetForm.getValue('target_controls'))"
                  i18n>This target has modified advanced settings.</span>
            <hr />
          </div>
        </div>

        <!-- Portals -->
        <div class="form-group"
             [ngClass]="{'has-error': targetForm.showError('portals', formDir)}">
          <label class="control-label col-sm-3"
                 for="portals">
            <ng-container i18n>Portals</ng-container>
            <span class="required"></span>
          </label>
          <div class="col-sm-9">

            <ng-container *ngFor="let portal of portals.value; let i = index">
              <div class="input-group cd-mb">
                <input class="form-control"
                       type="text"
                       [value]="portal"
                       disabled />
                <span class="input-group-btn">
                  <button class="btn btn-default"
                          type="button"
                          (click)="removePortal(i, portal)">
                    <i class="fa fa-remove fa-fw"
                       aria-hidden="true"></i>
                  </button>
                </span>
              </div>
            </ng-container>

            <span class="help-block"
                  *ngIf="targetForm.showError('portals', formDir, 'minGateways')"
                  i18n>At least {{ minimum_gateways }} gateways are required.</span>

            <div class="row">
              <div class="col-md-12">
                <cd-select [data]="portals.value"
                           [options]="portalsSelections"
                           [messages]="messages.portals"
                           (selection)="onPortalSelection($event)"
                           elemClass="btn btn-default pull-right">
                  <i class="fa fa-fw fa-plus"></i>
                  <ng-container i18n>Add portal</ng-container>
                </cd-select>
              </div>
            </div>

            <hr />
          </div>
        </div>

        <!-- Images -->
        <div class="form-group"
             [ngClass]="{'has-error': targetForm.showError('disks', formDir)}">
          <label class="control-label col-sm-3"
                 for="disks"
                 i18n>Images</label>
          <div class="col-sm-9">
            <ng-container *ngFor="let image of targetForm.getValue('disks'); let i = index">
              <div class="input-group cd-mb">
                <input class="form-control"
                       type="text"
                       [value]="image"
                       disabled />
                <span class="input-group-btn">
                  <button class="btn btn-default"
                          type="button"
                          (click)="imageSettingsModal(image)">
                    <i class="fa fa-cogs fa-fw"
                       aria-hidden="true"></i>
                  </button>
                  <button class="btn btn-default"
                          type="button"
                          (click)="removeImage(i, image)">
                    <i class="fa fa-remove fa-fw"
                       aria-hidden="true"></i>
                  </button>
                </span>

              </div>

              <span class="help-block">
                <ng-container *ngIf="backstores.length > 1"
                              i18n>Backstore: {{ imagesSettings[image].backstore }}.&nbsp;</ng-container>

                <ng-container *ngIf="hasAdvancedSettings(imagesSettings[image][imagesSettings[image].backstore])"
                              i18n>This image has modified settings.</ng-container>
              </span>
            </ng-container>

            <span class="help-block"
                  *ngIf="targetForm.showError('disks', formDir, 'required')"
                  i18n>At least 1 image is required.</span>

            <div class="row">
              <div class="col-md-12">
                <cd-select [data]="disks.value"
                           [options]="imagesSelections"
                           [messages]="messages.images"
                           (selection)="onImageSelection($event)"
                           elemClass="btn btn-default pull-right">
                  <i class="fa fa-fw fa-plus"></i>
                  <ng-container i18n>Add image</ng-container>
                </cd-select>
              </div>
            </div>

            <hr />
          </div>
        </div>

        <!-- acl_enabled -->
        <div class="form-group">
          <div class="col-sm-offset-3 col-sm-9">
            <div class="checkbox checkbox-primary">
              <input type="checkbox"
                     formControlName="acl_enabled"
                     name="acl_enabled"
                     id="acl_enabled">
              <label for="acl_enabled"
                     i18n>ACL authentication</label>
            </div>

            <hr />
          </div>
        </div>

        <!-- Initiators -->
        <div class="form-group"
             *ngIf="targetForm.getValue('acl_enabled')">
          <label class="control-label col-sm-3"
                 for="initiators"
                 i18n>Initiators</label>
          <div class="col-sm-9"
               formArrayName="initiators">
            <div class="panel panel-default"
                 *ngFor="let initiator of initiators.controls; let ii = index"
                 [formGroupName]="ii">
              <div class="panel-heading">
                <ng-container i18n>Initiator</ng-container>: {{ initiator.getValue('client_iqn') }}
                <button type="button"
                        class="close"
                        (click)="removeInitiator(ii)">
                  <i class="fa fa-remove fa-fw"></i>
                </button>
              </div>
              <div class="panel-body">
                <!-- Initiator: Name -->
                <div class="form-group"
                     [ngClass]="{'has-error': initiator.showError('client_iqn', formDir)}">
                  <label class="control-label col-sm-3"
                         for="client_iqn">
                    <ng-container i18n>Client IQN</ng-container>
                    <span class="required"></span>
                  </label>
                  <div class="col-sm-9">
                    <input class="form-control"
                           type="text"
                           formControlName="client_iqn"
                           (blur)="updatedInitiatorSelector()">

                    <span class="help-block"
                          *ngIf="initiator.showError('client_iqn', formDir, 'notUnique')"
                          i18n>Initiator IQN needs to be unique.</span>

                    <span class="help-block"
                          *ngIf="initiator.showError('client_iqn', formDir, 'required')"
                          i18n>This field is required.</span>

                    <span class="help-block"
                          *ngIf="initiator.showError('client_iqn', formDir, 'pattern')"
                          i18n>IQN has wrong pattern.</span>
                  </div>
                </div>

                <ng-container formGroupName="auth">
                  <!-- Initiator: User -->
                  <div class="form-group"
                       [ngClass]="{'has-error': initiator.showError('user', formDir)}">
                    <label class="control-label col-sm-3"
                           for="user"
                           i18n>User</label>
                    <div class="col-sm-9">
                      <input id="user"
                             class="form-control"
                             formControlName="user"
                             type="text">
                      <span class="help-block"
                            *ngIf="initiator.showError('user', formDir, 'required')"
                            i18n>This field is required.</span>

                      <span class="help-block"
                            *ngIf="initiator.showError('user', formDir, 'pattern')"
                            i18n>Usernames must have a length of 8 to 64 characters and
                        can only contain letters, '.', '@', '-', '_' or ':'.</span>
                    </div>
                  </div>

                  <!-- Initiator: Password -->
                  <div class="form-group"
                       [ngClass]="{'has-error': initiator.showError('password', formDir)}">
                    <label class="control-label col-sm-3"
                           for="password"
                           i18n>Password</label>
                    <div class="col-sm-9">
                      <div class="input-group">
                        <input id="password"
                               class="form-control"
                               formControlName="password"
                               type="password">

                        <span class="input-group-btn">
                          <button type="button"
                                  class="btn btn-default"
                                  cdPasswordButton="password">
                          </button>
                          <button type="button"
                                  class="btn btn-default"
                                  cdCopy2ClipboardButton="password">
                          </button>
                        </span>
                      </div>
                      <span class="help-block"
                            *ngIf="initiator.showError('password', formDir, 'required')"
                            i18n>This field is required.</span>

                      <span class="help-block"
                            *ngIf="initiator.showError('password', formDir, 'pattern')"
                            i18n>Passwords must have a length of 12 to 16 characters
                        and can only contain letters, '@', '-', '_' or '/'.</span>
                    </div>
                  </div>


                  <!-- Initiator: mutual_user -->
                  <div class="form-group"
                       [ngClass]="{'has-error': initiator.showError('mutual_user', formDir)}">
                    <label class="control-label col-sm-3"
                           for="mutual_user">
                      <ng-container i18n>Mutual User</ng-container>
                    </label>
                    <div class="col-sm-9">
                      <input id="mutual_user"
                             class="form-control"
                             formControlName="mutual_user"
                             type="text">

                      <span class="help-block"
                            *ngIf="initiator.showError('mutual_user', formDir, 'required')"
                            i18n>This field is required.</span>

                      <span class="help-block"
                            *ngIf="initiator.showError('mutual_user', formDir, 'pattern')"
                            i18n>Usernames must have a length of 8 to 64 characters and
                        can only contain letters, '.', '@', '-', '_' or ':'.</span>
                    </div>
                  </div>

                  <!-- Initiator: mutual_password -->
                  <div class="form-group"
                       [ngClass]="{'has-error': initiator.showError('mutual_password', formDir)}">
                    <label class="control-label col-sm-3"
                           for="mutual_password"
                           i18n>Mutual Password</label>
                    <div class="col-sm-9">
                      <div class="input-group">
                        <input id="mutual_password"
                               class="form-control"
                               formControlName="mutual_password"
                               type="password">

                        <span class="input-group-btn">
                          <button type="button"
                                  class="btn btn-default"
                                  cdPasswordButton="mutual_password">
                          </button>
                          <button type="button"
                                  class="btn btn-default"
                                  cdCopy2ClipboardButton="mutual_password">
                          </button>
                        </span>
                      </div>
                      <span class="help-block"
                            *ngIf="initiator.showError('mutual_password', formDir, 'required')"
                            i18n>This field is required.</span>

                      <span class="help-block"
                            *ngIf="initiator.showError('mutual_password', formDir, 'pattern')"
                            i18n>Passwords must have a length of 12 to 16 characters and
                        can only contain letters, '@', '-', '_' or '/'.</span>
                    </div>
                  </div>
                </ng-container>

                <!-- Initiator: Images -->
                <div class="form-group"
                     [ngClass]="{'has-error': initiator.showError('luns', formDir)}">
                  <label class="control-label col-sm-3"
                         for="luns"
                         i18n>Images</label>
                  <div class="col-sm-9">
                    <ng-container *ngFor="let image of initiator.getValue('luns'); let li = index">
                      <div class="input-group cd-mb">
                        <input class="form-control"
                               type="text"
                               [value]="image"
                               disabled />
                        <span class="input-group-btn">
                          <button class="btn btn-default"
                                  type="button"
                                  (click)="removeInitiatorImage(initiator, li, ii, image)">
                            <i class="fa fa-remove fa-fw"
                               aria-hidden="true"></i>
                          </button>
                        </span>
                      </div>
                    </ng-container>

                    <span *ngIf="initiator.getValue('cdIsInGroup')"
                          i18n>Initiator belongs to a group. Images will be configure in the group.</span>

                    <div class="row"
                         *ngIf="!initiator.getValue('cdIsInGroup')">
                      <div class="col-md-12">
                        <cd-select [data]="initiator.getValue('luns')"
                                   [options]="imagesInitiatorSelections[ii]"
                                   [messages]="messages.initiatorImage"
                                   elemClass="btn btn-default pull-right">
                          <i class="fa fa-fw fa-plus"></i>
                          <ng-container i18n>Add image</ng-container>
                        </cd-select>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <div class="row">
              <div class="col-md-12">
                <span class="text-muted"
                      *ngIf="initiators.controls.length === 0"
                      i18n>No items added.</span>

                <button (click)="addInitiator(); false"
                        class="btn btn-default pull-right">
                  <i class="fa fa-fw fa-plus"></i>
                  <ng-container i18n>Add initiator</ng-container>
                </button>
              </div>
            </div>

            <hr />
          </div>
        </div>

        <!-- Groups -->
        <div class="form-group"
             *ngIf="targetForm.getValue('acl_enabled')"
             [ngClass]="{'has-error': targetForm.showError('groups', formDir)}">
          <label class="control-label col-sm-3"
                 for="initiators"
                 i18n>Groups</label>
          <div class="col-sm-9"
               formArrayName="groups">
            <div class="panel panel-default"
                 *ngFor="let group of groups.controls; let gi = index"
                 [formGroupName]="gi">
              <div class="panel-heading">
                <ng-container i18n>Group</ng-container>: {{ group.getValue('group_id') }}
                <button type="button"
                        class="close"
                        (click)="groups.removeAt(gi)">
                  <i class="fa fa-remove fa-fw"></i>
                </button>
              </div>
              <div class="panel-body">
                <!-- Group: group_id -->
                <div class="form-group">
                  <label class="control-label col-sm-3"
                         for="group_id">
                    <ng-container i18n>Name</ng-container>
                    <span class="required"></span>
                  </label>
                  <div class="col-sm-9">
                    <input class="form-control"
                           type="text"
                           formControlName="group_id">
                  </div>
                </div>

                <!-- Group: members -->
                <div class="form-group"
                     [ngClass]="{'has-error': group.showError('members', formDir)}">
                  <label class="control-label col-sm-3"
                         for="members">
                    <ng-container i18n>Initiators</ng-container>
                  </label>
                  <div class="col-sm-9">
                    <ng-container *ngFor="let member of group.getValue('members'); let i = index">
                      <div class="input-group cd-mb">
                        <input class="form-control"
                               type="text"
                               [value]="member"
                               disabled />
                        <span class="input-group-btn">
                          <button class="btn btn-default"
                                  type="button"
                                  (click)="removeGroupInitiator(group, i, gi)">
                            <i class="fa fa-remove fa-fw"
                               aria-hidden="true"></i>
                          </button>
                        </span>
                      </div>
                    </ng-container>

                    <div class="row">
                      <div class="col-md-12">
                        <cd-select [data]="group.getValue('members')"
                                   [options]="groupMembersSelections[gi]"
                                   [messages]="messages.groupInitiator"
                                   (selection)="onGroupMemberSelection($event)"
                                   elemClass="btn btn-default pull-right">
                          <i class="fa fa-fw fa-plus"></i>
                          <ng-container i18n>Add initiator</ng-container>
                        </cd-select>
                      </div>
                    </div>

                    <hr />
                  </div>
                </div>

                <!-- Group: disks -->
                <div class="form-group"
                     [ngClass]="{'has-error': group.showError('disks', formDir)}">
                  <label class="control-label col-sm-3"
                         for="disks">
                    <ng-container i18n>Images</ng-container>
                  </label>
                  <div class="col-sm-9">
                    <ng-container *ngFor="let disk of group.getValue('disks'); let i = index">
                      <div class="input-group cd-mb">
                        <input class="form-control"
                               type="text"
                               [value]="disk"
                               disabled />
                        <span class="input-group-btn">
                          <button class="btn btn-default"
                                  type="button"
                                  (click)="removeGroupDisk(group, i, gi)">
                            <i class="fa fa-remove fa-fw"
                               aria-hidden="true"></i>
                          </button>
                        </span>
                      </div>
                    </ng-container>

                    <div class="row">
                      <div class="col-md-12">
                        <cd-select [data]="group.getValue('disks')"
                                   [options]="groupDiskSelections[gi]"
                                   [messages]="messages.initiatorImage"
                                   elemClass="btn btn-default pull-right">
                          <i class="fa fa-fw fa-plus"></i>
                          <ng-container i18n>Add image</ng-container>
                        </cd-select>
                      </div>
                    </div>

                    <hr />
                  </div>
                </div>
              </div>
            </div>

            <div class="row">
              <div class="col-md-12">
                <span class="text-muted"
                      *ngIf="groups.controls.length === 0"
                      i18n>No items added.</span>

                <button (click)="addGroup(); false"
                        class="btn btn-default pull-right">
                  <i class="fa fa-fw fa-plus"></i>
                  <ng-container i18n>Add group</ng-container>
                </button>
              </div>
            </div>
          </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>

./iscsi-target-form.component.scss

.cd-mb {
  margin-bottom: 10px;
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""