diff options
author | 2018-07-10 15:57:37 +0300 | |
---|---|---|
committer | 2018-07-10 15:57:37 +0300 | |
commit | ff76b5ed0aa91d5fdf9dc4f95e8b20f91ed9d072 (patch) | |
tree | aae42404a93fdffdd16ff050eaa28129959f7577 /vid-webpack-master/src/app/components | |
parent | c72d565bb58226b20625b2bce5f0019046bee649 (diff) |
New Angular UI from 1806
Change-Id: I39c160db0e0a6ec2e587ccf007ee1b23c6a08666
Issue-ID: VID-208
Signed-off-by: Sonsino, Ofir (os0695) <os0695@intl.att.com>
Diffstat (limited to 'vid-webpack-master/src/app/components')
29 files changed, 3528 insertions, 0 deletions
diff --git a/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.component.ts b/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.component.ts new file mode 100644 index 00000000..921c923d --- /dev/null +++ b/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.component.ts @@ -0,0 +1,80 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {DynamicInput, DynamicMultiSelect, DynamicNumber, DynamicSelect} from "../../shared/models/dynamicInput"; + +@Component({ + selector: 'dynamic-inputs', + templateUrl: './dynamic-inputs.html', + styleUrls: ['./dynamic-inputs.scss'] +}) + +export class DynamicInputsComponent implements OnInit{ + @Input() public list:any[] = []; + @Input() public group: FormGroup; + + private dynamicList:DynamicInput<any>[] = []; + + isDynamicNumber(item: any): item is DynamicNumber { + return item; + } + + buildValidators(item: DynamicInput<any>) { + let validatorArr = []; + item.maxLength && validatorArr.push(Validators.maxLength(item.maxLength)); + item.minLength && validatorArr.push(Validators.minLength(item.minLength)); + if(this.isDynamicNumber(item)) { + item.max && validatorArr.push(Validators.max(item.max)); + item.min && validatorArr.push(Validators.min(item.min)); + } + return Validators.compose(validatorArr); + } + + ngOnInit(): void { + this.list.forEach((item)=> { + let dynamicItem: DynamicInput<any>; + switch (item.type) { + case 'multi_select': + item.optionList.forEach(function(option) { option.id = option.id||option.name; + option.itemName = option.name;}); + item.settings = { + disabled: !item.isEnabled, + text: item.prompt, + }; + dynamicItem = new DynamicMultiSelect(item); + break; + case 'select': + let defaultValue:any = item.optionList.find((option) => option.isDataLoading && option.name); + item.value = defaultValue && defaultValue.id; + dynamicItem = new DynamicSelect(item); + break; + case 'boolean': + item.value = item.value || false; + item.optionList = [{name: true, isPermitted: true, isDataLoading: item.value}, {name: false, isPermitted: true, isDataLoading: !item.value}]; + + dynamicItem = new DynamicSelect(item); + break; + case 'number': + dynamicItem = new DynamicNumber(item); + break; + case 'file': + dynamicItem = new DynamicInput<any>(item); + break; + case 'checkbox': + dynamicItem = new DynamicInput<boolean>(item); + break; + case 'map': + item.prompt = "{<key1>: <value1>,\.\.\.,<keyN>: <valueN>}"; + dynamicItem = new DynamicInput<string>(item); + break; + case 'list': + item.prompt = "[<value1>,...,<valueN>]"; + dynamicItem = new DynamicInput<string>(item); + break; + default: dynamicItem = new DynamicInput<string>(item); + } + this.dynamicList.push(dynamicItem); + this.group.addControl(dynamicItem.name, new FormControl({value: dynamicItem.value, disabled: !dynamicItem.isEnabled}, this.buildValidators(dynamicItem))); + }) + } + +} diff --git a/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.html b/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.html new file mode 100644 index 00000000..a933364e --- /dev/null +++ b/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.html @@ -0,0 +1,22 @@ +<div *ngFor="let item of dynamicList"> + <div id="{{item.id}}" class="details-item" [ngSwitch]="item.type" [formGroup]="group" [hidden]="!item.isVisible"> + <label>{{item.name | dynamicInputLabel }}</label> + <select *ngSwitchCase="item.type === 'select'|| item.type === 'boolean'? item.type: ''" name="{{item.name}}" class="form-control input-text" [formControlName]="item.name" title="{{item.description}}" [required]="item.isRequired && item.isVisible" maxlength="{{item.maxLength}}" minlength="{{item.minLength}}"> + <option value="null" [selected]="item.value == null || item.value == undefined" hidden disabled>{{item.prompt}}</option> + <option *ngFor="let option of item.optionList" [ngValue]="option.id || option.name" [disabled]="!option.isPermitted" [selected]="option.isDataLoading">{{option.name}}</option> + </select> + <angular2-multiselect *ngSwitchCase="'multi_select'" [formControlName]="item.name" [(ngModel)]="item.selectedItems" [data]="item.optionList" [settings]="item.settings" title="{{item.description}}" [required]="item.isRequired && item.isVisible"></angular2-multiselect> + <input *ngSwitchCase="'number'" name="{{item.name}}" class="form-control input-text" [formControlName]="item.name" type="number" placeholder="{{item.prompt}}" title="{{item.description}}" min="{{item.min}}" max="{{item.max}}" [readonly]="item.isReadOnly" [required]="item.isRequired && item.isVisible"> + <div *ngSwitchCase="'file'"> + + <label class="dynamicFileName" for="dynamicFileInput-{{item.name}}"> + <input id="dynamicFileInput-{{item.name}}" name="{{item.name}}" class="form-control input-text" [formControlName]="item.name" type="file" placeholder="{{item.prompt}}" [readonly]="item.isReadOnly" [required]="item.isRequired && item.isVisible"> + <label for="dynamicFileInput-{{item.name}}" class="labelForImage"> + <span class="icon-browse"></span> + </label> + </label> + </div> + <input *ngSwitchCase="'checkbox'" name="{{item.name}}" [formControlName]="item.name" type="checkbox" data-toggle="toggle" title="{{item.description}}" [readonly]="item.isReadOnly" [required]="item.isRequired && item.isVisible"> + <input *ngSwitchDefault name="{{item.name}}" class="form-control input-text" [formControlName]="item.name" placeholder="{{item.prompt}}" title="{{item.description}}" maxlength="{{item.maxLength}}" minlength="{{item.minLength}}" [readonly]="item.isReadOnly" [required]="item.isRequired && item.isVisible"/> + </div> +</div> diff --git a/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.scss b/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.scss new file mode 100644 index 00000000..11a14142 --- /dev/null +++ b/vid-webpack-master/src/app/components/dynamic-inputs/dynamic-inputs.scss @@ -0,0 +1,58 @@ +input[type=file] { + opacity: 0; + position: relative; + z-index: 1; + width: 0.5px; + height: 0.5px; + display: inline-block; + input { + display: none; + } +} + +.dynamicFileName { + width: 100%; + height: 34px; + background: #FFFFFF; + border: 1px solid #D2D2D2; + border-radius: 2px; + display: inline-block; + line-height: 34px; + font-weight: normal !important; + padding-left: 3px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} + +.labelForImage { + background: #F2F2F2; + border-left: 1px solid #D2D2D2; + float: right; + height: 32px; +} + +.icon-browse:before { + content: "\e90d"; + color: #5A5A5A; + font-size: 15px; + cursor: pointer; + width: 34px; + height: 100%; + line-height: 34px; + text-align: center; + display: inline-block; + vertical-align: super; + border-radius: 2px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.icon-browse:hover::before { + background-color: #E6F6FB; + color: #009FDB; +} + +.icon-browse:active::before { + background-color: #E6F6FB; + color: #009FDB; +} diff --git a/vid-webpack-master/src/app/components/form-async/form-async.component.ts b/vid-webpack-master/src/app/components/form-async/form-async.component.ts new file mode 100644 index 00000000..e71c4446 --- /dev/null +++ b/vid-webpack-master/src/app/components/form-async/form-async.component.ts @@ -0,0 +1,80 @@ +import {Component, Input, OnInit, ViewChild} from '@angular/core'; +import { NgRedux, select } from '@angular-redux/store'; +import { Observable } from "rxjs/Observable"; +import { updateProductFamilies } from "../../service.actions"; +import { AppState } from "../../store/reducers"; +import { + loadProductFamiliesAction, loadLcpTenant, loadAicZones, + loadCategoryParameters +} from '../../services/aaiService/aai.actions'; +import { LcpRegionsAndTenants } from "../../shared/models/lcpRegionsAndTenants"; +import {NgForm} from "@angular/forms"; +import {SelectOption} from "../../shared/models/selectOption"; +import {VNFModel} from "../../shared/models/vnfModel"; + +@Component({ + selector: "formasync", + templateUrl: "form-async.template.html", + styleUrls: ["form-async.style.scss"], + +}) + +export class formasync implements OnInit { + + constructor(private store: NgRedux<AppState>) { } + + @ViewChild('form') form: NgForm; + + @Input() + set model(model: VNFModel) { + if (model) { + this.isUserProvidedNaming = model.isUserProvidedNaming; + } + }; + + @select(['service', 'productFamilies']) + readonly productFamilies: Observable<any>; + + @select(['service', 'lcpRegionsAndTenants']) + readonly lcpRegionsAndTenants: Observable<any>; + + @select(['service', 'lcpRegionsAndTenants', 'lcpRegionList']) + readonly lcpRegions: Observable<any>; + + @select(['service', 'aicZones']) + readonly aicZones: Observable<any>; + + @select(['service', 'categoryParameters', 'platformList']) + readonly platformList: Observable<any>; + + @select(['service', 'categoryParameters', 'lineOfBusinessList']) + readonly lineOfBusinessList: Observable<any>; + + rollbackOnFailure = [ + new SelectOption({id: 'true', name: 'Rollback'}), + new SelectOption({id: 'false', name: 'Don\'t Rollback'}) + ]; + tenants = []; + + serviceInstance: any = { + rollback:'true' + }; + + isUserProvidedNaming: boolean = false; + + onLcpSelect(newValue: string) { + let value: LcpRegionsAndTenants = undefined; + this.lcpRegionsAndTenants.subscribe(data => value = data); + this.tenants = value.lcpRegionsTenantsMap[newValue]; + } + + ngOnInit() { + this.store.dispatch(loadProductFamiliesAction()); + this.store.dispatch(loadLcpTenant()); + this.store.dispatch(loadAicZones()); + this.store.dispatch(loadCategoryParameters()); + } +} + + + diff --git a/vid-webpack-master/src/app/components/form-async/form-async.style.scss b/vid-webpack-master/src/app/components/form-async/form-async.style.scss new file mode 100644 index 00000000..e6c89bca --- /dev/null +++ b/vid-webpack-master/src/app/components/form-async/form-async.style.scss @@ -0,0 +1,15 @@ +.form-wrapper{ + width:640px; + padding-left: 50px; + padding-top: 50px; + .details-item{ + padding-bottom: 30px; + } + .details-item { + select { + font-family: OpenSans-Italic; + font-size: 14px; + color: #959595 !important; + } + } +} diff --git a/vid-webpack-master/src/app/components/form-async/form-async.template.html b/vid-webpack-master/src/app/components/form-async/form-async.template.html new file mode 100644 index 00000000..c63a7bee --- /dev/null +++ b/vid-webpack-master/src/app/components/form-async/form-async.template.html @@ -0,0 +1,67 @@ +<div class="content"> + <form #form="ngForm" name="networkNodeForm" class="form-wrapper"> + <div *ngIf="isUserProvidedNaming" class="details-item"> + <label class="placeholder">Instance name*</label> + <input [attr.data-tests-id]="'instanceName'" id="instance-name" [(ngModel)]="serviceInstance.instanceName" name="instance-name" + class="form-control input-text" placeholder="Type Instance Name" type="text" required> + </div> + + <div class="details-item"> + <label>Product family</label> + <select class="form-control input-text" [(ngModel)]="serviceInstance.productFamily" data-tests-id="productFamily" id="product-family-select" + name="product-family-select"> + <option class="placeholder" [value]="undefined" disabled>Select Product Family</option> + <option *ngFor="let productFamily of productFamilies | async" [value]="productFamily.id" [disabled]="!productFamily.isPermitted">{{productFamily.name}}</option> + </select> + </div> + + <div class="details-item"> + <label>LCP region:*</label> + <select (change)="onLcpSelect($event.target.value)" class="form-control input-text" [(ngModel)]="serviceInstance.lcpRegion" + name="lcpRegion" id="lcpRegion-select" data-tests-id="lcpRegion" required> + <option class="placeholder1" [value]="undefined" disabled>Select LCP Region</option> + <option *ngFor="let lcpRegion of lcpRegions | async" [value]="lcpRegion.id" [disabled]="!lcpRegion.isPermitted" class="lcpRegionOption">{{lcpRegion.id}}</option> + </select> + </div> + <div class="details-item"> + <label>Tenant:*</label> + <select class="form-control input-text" [(ngModel)]="serviceInstance.tenantId" name="tenant" id="tenant-select" data-tests-id="tenant" + required> + <option class="placeholder1" [value]="undefined" disabled>Select Tenant</option> + <option *ngFor="let tenant of tenants" [value]="tenant.id" [disabled]="!tenant.isPermitted">{{tenant.name}}</option> + </select> + </div> + + <div class="details-item"> + <label>AIC Zone:</label> + <select class="form-control input-text" name="aicZone" id="aicZone-select" data-tests-id="aic_zone" [(ngModel)]="serviceInstance.aicZone"> + <option class="placeholder1" [value]="undefined" disabled>Select AIC Zone</option> + <option class="aicZoneOption" *ngFor="let aicZone of aicZones | async" [value]="aicZone.id">{{aicZone.name}}</option> + </select> + </div> + <div class="details-item"> + <label>Platform:</label> + <select data-tests-id="platform" class="form-control input-text" name="platform" id="platform" [(ngModel)]="serviceInstance.platformName"> + <option class="placeholder1" [value]="undefined" disabled>Select Platform</option> + <option *ngFor="let platform of platformList | async" [value]="platform.id">{{platform.name}}</option> + </select> + </div> + + <div class="details-item"> + <label>Line Of Business:*</label> + <select data-tests-id="lineOfBusiness" class="form-control input-text" [(ngModel)]="serviceInstance.lineOfBusiness" + name="owningEntity" id="owningEntity" required> + <option class="placeholder1" [value]="undefined" disabled>Select Line Of Business</option> + <option *ngFor="let lineOfBusiness of lineOfBusinessList | async" [value]="lineOfBusiness.id">{{lineOfBusiness.name}}</option> + </select> + </div> + <div class="details-item"> + <label>Rollback On Failure:</label> + <select data-tests-id="suppressRollback" class="form-control input-text" name="rollbackOnFailure" id="rollbackOnFailure" [(ngModel)]="serviceInstance.rollback"> + <option *ngFor="let option of rollbackOnFailure" [value]="option.id">{{option.name}}</option> + </select> + </div> + </form> + +</div> + diff --git a/vid-webpack-master/src/app/components/instance-popup/instance-popup.components.ts b/vid-webpack-master/src/app/components/instance-popup/instance-popup.components.ts new file mode 100644 index 00000000..b8ce613d --- /dev/null +++ b/vid-webpack-master/src/app/components/instance-popup/instance-popup.components.ts @@ -0,0 +1,9 @@ +import {ModelInformationItem} from "../../shared/components/model-information/model-information.component"; + + +export interface InstancePopup { + onCancelClick():void; + createModelInformationItems(): Array<ModelInformationItem>; + getModelName():string; +} + diff --git a/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.component.ts b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.component.ts new file mode 100644 index 00000000..b6a2e396 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.component.ts @@ -0,0 +1,238 @@ +import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {ServicePopupDataModel} from './servicePopupDataModel'; +import {AaiService} from '../../../services/aaiService/aai.service'; +import {updateServiceInstance} from "../../../service.actions"; +import * as _ from 'lodash'; +import {ServiceModel} from "../../../shared/models/serviceModel"; +import {ModelInfo} from "../../../shared/models/modelInfo"; +import {loadProductFamiliesAction} from "../../../services/aaiService/aai.actions"; +import {Observable} from "rxjs/Observable"; +import {SelectOptionInterface} from "../../../shared/models/selectOption"; +import {NgRedux, select} from "@angular-redux/store"; +import {AppState} from "../../../store/reducers"; +import {isNullOrUndefined} from 'util'; +import {ServiceInstanceDetailsService} from './service-instance-details.service'; +import {NumbersLettersUnderscoreValidator} from '../../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; +import {DefaultDataGeneratorService} from '../../../shared/services/defaultDataServiceGenerator/default.data.generator.service'; + + +@Component({ + selector: 'service-instance-details', + templateUrl: 'service-instance-details.html', + styleUrls: ['service-instance-details.scss'], + providers: [AaiService] +}) + +export class ServiceInstanceDetailsComponent implements OnInit, OnChanges { + ngOnChanges(changes: SimpleChanges): void { + if (changes["serviceInstance"] !== undefined && changes["serviceInstance"].currentValue !== changes["serviceInstance"].previousValue && changes["serviceInstance"].currentValue !== null) { + this.oldServiceInstance = Object.assign({}, this.serviceInstance); + } + } + _serviceModel: ServiceModel; + @Input () serviceInstance: any; + @Input () dynamicInputs; + @Input () servicesQty: number; + @Input () + set serviceModel(serviceModel: ServiceModel) { + this._serviceModel = serviceModel; + this.updateFormGroupControlsWithServiceModel(serviceModel); + } + @ViewChild('serviceForm') serviceForm: 'ServiceForm'; + @Output() closePopup : EventEmitter<any> = new EventEmitter<any>(); + @Output() onDataChanged: EventEmitter<any> = new EventEmitter<any>(); + oldServiceInstance = {}; + + //todo: implement Epics and use @select to fetch the rest of the form's data as done with productFamilies. + //that way we can loose the updateFormData function and the subscription to store in the constructor. + @select(['service','productFamilies']) + readonly productFamilies : Observable<SelectOptionInterface[]>; + serviceDetails:any = { + + }; + servicePopupDataModel: ServicePopupDataModel = new ServicePopupDataModel(); + serviceInstanceDetailsFormGroup: FormGroup; + serviceInstanceDetailsService : ServiceInstanceDetailsService; + + constructor(private _aaiService: AaiService, private store: NgRedux<AppState>, private _serviceInstanceDetailsService : ServiceInstanceDetailsService, private _defaultDataGeneratorService : DefaultDataGeneratorService) { + this.store.subscribe(() => {this.updateFormData()}); + this.serviceInstanceDetailsService = this._serviceInstanceDetailsService; + this.serviceInstanceDetailsFormGroup = this.createFormGroup(); + + this.serviceInstanceDetailsFormGroup.valueChanges.subscribe(()=> { + this.onDataChanged.next(); + }) + } + + ngOnInit() { + this.subscribeToFormChanges(); + this._aaiService.getSubscribers().subscribe(); + this._aaiService.getCategoryParameters(null).subscribe(); + this._aaiService.getAicZones().subscribe(); + this.store.dispatch(loadProductFamiliesAction()); + } + + + createFormGroup(): FormGroup { + const formGroup = new FormGroup({ + globalSubscriberId: new FormControl( + Validators.compose([Validators.required]) + ), + productFamilyId: new FormControl(), + subscriptionServiceType: new FormControl({value: null, disabled: true}, Validators.compose([Validators.required])), + lcpCloudRegionId: new FormControl({value: null, disabled: true}, Validators.compose([Validators.required])), + tenantId: new FormControl({value: null, disabled: true}, Validators.compose([Validators.required])), + aicZoneId: new FormControl(), + projectName: new FormControl(), + owningEntityId: new FormControl(Validators.compose([Validators.required])), + rollbackOnFailure: new FormControl(null, Validators.required), + }); + + return formGroup; + } + + updateFormGroupControlsWithServiceModel(serviceModel: ServiceModel) { + this.serviceInstanceDetailsFormGroup.markAsUntouched(); + + if (serviceModel) { + this.serviceDetails.isUserProvidedNaming = serviceModel.isUserProvidedNaming; + if (serviceModel.isUserProvidedNaming) { + this.serviceInstanceDetailsFormGroup.addControl('instanceName', new FormControl('', Validators.compose([Validators.required, NumbersLettersUnderscoreValidator.valid]))) + }else{ + this.serviceInstanceDetailsFormGroup.removeControl('instanceName'); + } + + if (serviceModel.isMultiStepDesign) { + this.serviceInstanceDetailsFormGroup.addControl('pause', new FormControl(true)); + }else{ + this.serviceInstanceDetailsFormGroup.removeControl('pause'); + } + } + } + + updateFormData() { + let service = this.store.getState().service; + this.servicePopupDataModel.subscribers = service.subscribers; + this.servicePopupDataModel.serviceTypes = service.serviceTypes[this.servicePopupDataModel.globalCustomerId]; + this.servicePopupDataModel.lcpRegions = service.lcpRegionsAndTenants.lcpRegionList; + if (this.serviceInstance) { + this.servicePopupDataModel.tenants = service.lcpRegionsAndTenants.lcpRegionsTenantsMap[this.serviceInstance.lcpCloudRegionId]; + } + this.servicePopupDataModel.aicZones = service.aicZones; + this.servicePopupDataModel.owningEntities = _.get(service.categoryParameters, 'owningEntityList'); + this.servicePopupDataModel.projects = _.get(service.categoryParameters, 'projectList'); + this.onDataChanged.next(); + } + + subscribeToFormChanges(): void { + this.serviceInstanceDetailsFormGroup.get('globalSubscriberId').valueChanges.subscribe(val => { + this.updateServiceTypes(val); + this.setDisabledState(val, 'subscriptionServiceType'); + + }); + this.serviceInstanceDetailsFormGroup.get('subscriptionServiceType').valueChanges.subscribe(val => { + this.getTenants(val); + this.setDisabledState(val, 'lcpCloudRegionId'); + + }); + this.serviceInstanceDetailsFormGroup.get('lcpCloudRegionId').valueChanges.subscribe(val => { + this.setDisabledState(val, 'tenantId'); + this.updateTenantList(val); + + }); + + this.serviceInstanceDetailsFormGroup.get('tenantId').valueChanges.subscribe(val => { + this.serviceDetails.tenantName = this.getNameFromListById(this.servicePopupDataModel.tenants, val); + this.onDataChanged.next(); + }); + + this.serviceInstanceDetailsFormGroup.get('aicZoneId').valueChanges.subscribe(val => { + this.serviceDetails.aicZoneName = this.getNameFromListById(this.servicePopupDataModel.aicZones, val); + this.onDataChanged.next(); + }); + } + + getNameFromListById(list, id:string ) { + if(list && id) { + let filterItem = list.filter(item => { + return item.id == id; + }) + return filterItem && filterItem[0].name; + } + return null; + } + + setDisabledState(val, field: string): void { + if(val) { + this.serviceInstanceDetailsFormGroup.controls[field].enable(); + } else { + this.serviceInstanceDetailsFormGroup.controls[field].disable(); + } + } + + isShowingNotificationArea(): boolean { + return this.servicesQty > 1; + } + + updateServiceTypes(subscriberId) { + if (subscriberId) { + this.servicePopupDataModel.globalCustomerId = subscriberId; + this._aaiService.getServiceTypes(subscriberId).subscribe(() => { + this.updateFormData(); + this.onDataChanged.next(); + }, (error) => { + + }); + } + } + + updateTenantList(cloudRegionId) { + this.servicePopupDataModel.tenants = this.store.getState().service.lcpRegionsAndTenants.lcpRegionsTenantsMap[cloudRegionId]; + this.onDataChanged.next(); + } + + getTenants(serviceType) { + if (serviceType) { + this._aaiService.getLcpRegionsAndTenants(this.servicePopupDataModel.globalCustomerId, serviceType).subscribe(()=>{ + this.onDataChanged.next(); + }); + } + } + + onSubmit(formValues): void { + formValues.bulkSize = this.servicesQty; + let dynamicFields: { [dynamicField: string] : string; }; + dynamicFields = {}; + this.dynamicInputs.map(function (x) { + let dynamicField: string = x.id; + dynamicFields[dynamicField] = formValues[dynamicField]; + delete formValues[dynamicField]; + }); + formValues.instanceParams = []; + formValues.instanceParams.push(dynamicFields); + formValues.modelInfo = new ModelInfo(this._serviceModel); + Object.assign(formValues, this.serviceDetails); + this.store.dispatch(updateServiceInstance(formValues, this._serviceModel.uuid)); + if (this.store.getState().global.flags['FLAG_SETTING_DEFAULTS_IN_DRAWING_BOARD']){ + this._defaultDataGeneratorService.updateReduxOnFirstSet(this._serviceModel.uuid,formValues); + } + window.parent.postMessage( { + eventId: 'submitIframe', + data: { + serviceModelId: this._serviceModel.uuid + } + }, "*"); + this.closePopup.emit(this._serviceModel.uuid); + } + + hasApiError(controlName : string, data : Array<any>){ + if(!isNullOrUndefined(this.servicePopupDataModel) && !isNullOrUndefined(data)){ + if(!this.serviceInstanceDetailsFormGroup.controls[controlName].disabled && data.length === 0){ + return true; + } + } + return false; + } + +} diff --git a/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.html b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.html new file mode 100644 index 00000000..3d632bd8 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.html @@ -0,0 +1,168 @@ + +<div id="service-instance-details"> + <form id="serviceForm" #serviceForm="ngForm" (ngSubmit)="onSubmit(serviceForm.value)" [formGroup]="serviceInstanceDetailsFormGroup"> + <!--We can't use [hidden] since bootstrap.css label has display: inline-block. --> + <!--see https://stackoverflow.com/questions/34650410/angular-2-hidden-does-not-seem-to-be-working--> + <label id="notification-area" *ngIf="isShowingNotificationArea()">Data entered will apply to all service instances</label> + + <div class="details-item" *ngIf="serviceInstanceDetailsFormGroup.get('instanceName')"> + <label class="required">Instance name:</label> + <input patternInput + pattern="^[a-zA-Z0-9_]*$" + [attr.data-tests-id]="'instanceName'" + id="instance-name" + name="instance-name" + [ngClass]="{'error-style' :(serviceInstance?.instanceName != '' && serviceInstanceDetailsFormGroup.controls['instanceName']?.touched && serviceInstanceDetailsFormGroup.controls['instanceName']?.errors?.pattern !== null)}" + [formControlName]="'instanceName'" + class="form-control input-text" + placeholder="Type Instance Name" + type="text" + [(ngModel)]="serviceInstance.instanceName" required> + <form-control-error + *ngIf="serviceInstance?.instanceName != '' && serviceInstanceDetailsFormGroup.controls['instanceName']?.touched && serviceInstanceDetailsFormGroup.controls['instanceName']?.errors?.pattern !== null" + [message]="'Instance name may include only alphanumeric characters and underscore.'"></form-control-error> + + </div> + + <div class="details-item"> + <label class="required">Subscriber name:</label> + <select class="subscriber form-control input-text" id="subscriber-name-select" data-tests-id="subscriberName" + name="subscriber-name-select" [formControlName]="'globalSubscriberId'" + [ngClass]="{'error-style' :serviceInstanceDetailsService.hasApiError('globalSubscriberId',servicePopupDataModel?.subscribers, serviceInstanceDetailsFormGroup)}" + [(ngModel)]="serviceInstance.globalSubscriberId" + required> + <option [value]="undefined" disabled>Select Subscriber Name</option> + <option class="subscriberNameOption" *ngFor="let subscriber of servicePopupDataModel.subscribers" + [value]="subscriber.id" [disabled]="!subscriber.isPermitted">{{subscriber.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('globalSubscriberId',servicePopupDataModel?.subscribers, serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + + </div> + + <div class="details-item"> + <label class="required">Service type:</label> + <select class="form-control input-text" + [(ngModel)]="serviceInstance.subscriptionServiceType" + [formControlName]="'subscriptionServiceType'" + [ngClass]="{'error-style' :serviceInstanceDetailsService.hasApiError('subscriptionServiceType',servicePopupDataModel?.serviceTypes, serviceInstanceDetailsFormGroup)}" + data-tests-id="serviceType" id="service-type-select" + name="service-type" required> + <option [value]="undefined" disabled>Select Service Type</option> + <option *ngFor="let serviceType of servicePopupDataModel.serviceTypes" class="serviceTypeOption" [value]="serviceType.name" [disabled]="!serviceType.isPermitted">{{serviceType.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('subscriptionServiceType',servicePopupDataModel?.serviceTypes, serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div class="details-item"> + <label class="required">Product family:</label> + <select class="form-control input-text" + data-tests-id="productFamily" + id="product-family-select" + [ngClass]="{'error-style' :serviceInstanceDetailsService.hasApiError('productFamilyId',productFamilies, serviceInstanceDetailsFormGroup)}" + [formControlName]="'productFamilyId'" + [(ngModel)]="serviceInstance.productFamilyId" + name="product-family-select" required> + <option [value]="undefined" disabled>Select Product Family</option> + <option *ngFor="let productFamily of productFamilies | async" [value]="productFamily.id" + [disabled]="!productFamily.isPermitted">{{productFamily.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('productFamilyId',productFamilies, serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div class="details-item"> + <label class="required">LCP region:</label> + <select + class="form-control input-text" + [formControlName]="'lcpCloudRegionId'" + [(ngModel)]="serviceInstance.lcpCloudRegionId" + [ngClass]="{'error-style ' : serviceInstanceDetailsService.hasApiError('lcpCloudRegionId', servicePopupDataModel?.lcpRegions, serviceInstanceDetailsFormGroup)}" + name="lcpRegion" + id="lcpRegion-select" + data-tests-id="lcpRegion" + required> + <option [value]="undefined" disabled>Select LCP Region</option> + <option *ngFor="let lcpRegion of servicePopupDataModel.lcpRegions" [value]="lcpRegion.id" [disabled]="!lcpRegion.isPermitted" class="lcpRegionOption">{{lcpRegion.id}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('lcpCloudRegionId', servicePopupDataModel?.lcpRegions, serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div class="details-item"> + <label class="required">Tenant:</label> + <select class="form-control input-text" + [formControlName]="'tenantId'" + [(ngModel)]="serviceInstance.tenantId" + name="tenant" id="tenant-select" + [ngClass]="{'error-style ' : serviceInstanceDetailsService.hasApiError('tenantId',servicePopupDataModel?.tenants ,serviceInstanceDetailsFormGroup)}" + data-tests-id="tenant" required> + <option [value]="undefined" disabled>Select Tenant</option> + <option *ngFor="let tenant of servicePopupDataModel.tenants" [value]="tenant.id" [disabled]="!tenant.isPermitted">{{tenant.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('tenantId',servicePopupDataModel?.tenants ,serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div class="details-item"> + <label>AIC Zone:</label> + <select + class="form-control input-text" + name="aicZone" id="aicZone-select" + data-tests-id="aic_zone" + [formControlName]="'aicZoneId'" + [ngClass]="{'error-style ' : servicePopupDataModel?.aicZones?.length == 0 && serviceInstanceDetailsFormGroup.controls['aicZoneId'].disabled == false}" + [(ngModel)]="serviceInstance.aicZoneId" > + <option [value]="undefined" disabled>Select AIC Zone</option> + <option class="aicZoneOption" *ngFor="let aicZone of servicePopupDataModel.aicZones" [value]="aicZone.id">{{aicZone.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('aicZoneId',servicePopupDataModel?.aicZones ,serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + + </div> + + <div class="details-item"> + <label>Project:</label> + <select + [attr.data-tests-id]="'project'" + class="form-control input-text" + [ngClass]="{'error-style ' : servicePopupDataModel?.projects?.length == 0 && serviceInstanceDetailsFormGroup.controls['projectName'].disabled == false}" + name="project" id="project" + [formControlName]="'projectName'" + [(ngModel)]="serviceInstance.projectName" > + <option [value]="undefined" disabled>Select Project</option> + <option *ngFor="let project of servicePopupDataModel.projects" [value]="project.id">{{project.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('projectName',servicePopupDataModel?.projects ,serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + + </div> + + <div class="details-item"> + <label class="required">Owning entity:</label> + <select [attr.data-tests-id]="'owningEntity'" + class="form-control input-text" + [formControlName]="'owningEntityId'" + [(ngModel)]="serviceInstance.owningEntityId" + name="owningEntity" id="owningEntity" + [ngClass]="{'error-style ' : servicePopupDataModel?.owningEntities?.length == 0 && serviceInstanceDetailsFormGroup.controls['owningEntityId'].disabled == false}" + required> + <option [value]="undefined" disabled>Select Owning Entity</option> + <option *ngFor="let owningEntity of servicePopupDataModel.owningEntities" [value]="owningEntity.id">{{owningEntity.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('owningEntityId',servicePopupDataModel?.owningEntities ,serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + <div class="details-item"> + <label class="required">Rollback On Failure:</label> + <select [attr.data-tests-id]="'rollback'" + [ngClass]="{'error-style' :serviceInstanceDetailsService.hasApiError('rollbackOnFailure',servicePopupDataModel?.rollbackOnFailure, serviceInstanceDetailsFormGroup)}" + class="form-control input-text" + [(ngModel)]="serviceInstance.rollbackOnFailure" + [formControlName]="'rollbackOnFailure'" name="rollbackOnFailure" id="rollbackOnFailure"> + <option *ngFor="let option of servicePopupDataModel.rollbackOnFailure" [value]="option.id">{{option.name}}</option> + </select> + <form-control-error *ngIf="serviceInstanceDetailsService.hasApiError('rollbackOnFailure',servicePopupDataModel?.rollbackOnFailure, serviceInstanceDetailsFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + <div class="details-item" *ngIf="serviceInstanceDetailsFormGroup.get('pause')"> + <input #pause id="pause" [formControlName]="'pause'" [(ngModel)]="serviceInstance.pause" type="checkbox" name="pause" data-toggle="toggle"> + <label class="checkbox-label" for="pause">Pause on pause points:</label> + </div> + + + <dynamic-inputs *ngIf="dynamicInputs != undefined && dynamicInputs.length>0" [group]="serviceInstanceDetailsFormGroup" [list]="dynamicInputs"></dynamic-inputs> + </form> +</div> diff --git a/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.scss b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.scss new file mode 100644 index 00000000..928343d4 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.scss @@ -0,0 +1,64 @@ +#service-instance-details { + position: relative; + + #notification-area { + color: #959595; + font-size: 12px; + position: absolute; + top: 3px; + left: 30px; + } + + height: 100%; + overflow: auto; + padding: 30px; + + /deep/ { + .form-control { + border-radius: 2px; + box-shadow: none; + border-color: #D2D2D2; + } + + label { + font-family: OpenSans-Semibold; + font-size: 12px; + } + + select { + @extend .form-control; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: url('../../../../assets/img/chevron.svg') 0 0 no-repeat; + background-size: 24px; + background-position-x: right; + background-position-y: center; + font-family: OpenSans-Italic; + font-size: 14px; + color: #959595; + height: 38px; + } + + input:not([type='checkbox']) { + @extend .form-control; + height: 38px; + } + + .form-control[disabled], fieldset[disabled] .form-control { + opacity: 0.5; + } + .input-text { + border: 1px solid #D2D2D2; + border-radius: 2px; + } + + .details-item { + margin-bottom: 20px; + } + } + + .checkbox-label { + font-family: OpenSans-Regular; + } +} diff --git a/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.service.spec.ts b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.service.spec.ts new file mode 100644 index 00000000..605653bd --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.service.spec.ts @@ -0,0 +1,26 @@ +import { getTestBed, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { ServiceInstanceDetailsService } from './service-instance-details.service'; +import { NgRedux } from '@angular-redux/store'; + +export class MockAppStore<T> {} + +describe('Service instance details service', () => { + let injector; + let service: ServiceInstanceDetailsService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ServiceInstanceDetailsService, + {provide: NgRedux, useClass: MockAppStore}] + }); + + injector = getTestBed(); + service = injector.get(ServiceInstanceDetailsService); + httpMock = injector.get(HttpTestingController); + }); +}); + + diff --git a/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.service.ts b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.service.ts new file mode 100644 index 00000000..99b390d2 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-instance-details/service-instance-details.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { isNullOrUndefined } from 'util'; +import { FormGroup } from '@angular/forms'; +import * as _ from 'lodash'; +import { createVFModuleInstance, updateVFModuleInstance, updateVNFInstance } from '../../../service.actions'; +import { NgRedux } from '@angular-redux/store'; +import { AppState } from '../../../store/reducers'; + +@Injectable() +export class ServiceInstanceDetailsService { + static controlsFieldsStatus = {}; + + constructor(private store: NgRedux<AppState>) { } + hasApiError(controlName: string, data: Array<any>, serviceInstanceDetailsFormGroup: FormGroup) { + if (!isNullOrUndefined(data)) { + if (!serviceInstanceDetailsFormGroup.controls[controlName].disabled && data.length === 0) { + return true; + } + } + return false; + } +} diff --git a/vid-webpack-master/src/app/components/service-popup/service-instance-details/servicePopupDataModel.ts b/vid-webpack-master/src/app/components/service-popup/service-instance-details/servicePopupDataModel.ts new file mode 100644 index 00000000..c7894e2c --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-instance-details/servicePopupDataModel.ts @@ -0,0 +1,32 @@ +import {SelectOption, SelectOptionInterface} from "../../../shared/models/selectOption"; + +export class ServicePopupDataModel { + subscribers: SelectOptionInterface[]; + serviceTypes: SelectOptionInterface[]; + aicZones: SelectOptionInterface[]; + lcpRegions: SelectOptionInterface[]; + productFamilies: SelectOptionInterface[]; + lcpRegionsTenantsMap: object; + tenants: SelectOptionInterface[]; + projects: SelectOptionInterface[]; + owningEntities: SelectOptionInterface[]; + globalCustomerId: string; + rollbackOnFailure: SelectOptionInterface[]; + + + constructor(){ + this.subscribers = null; + this.serviceTypes = null; + this.aicZones = null; + this.lcpRegions = null; + this.lcpRegionsTenantsMap = {}; + this.tenants = null; + this.productFamilies = null; + this.projects = null; + this.owningEntities = null; + this.rollbackOnFailure = [ + new SelectOption({id: 'true', name: 'Rollback'}), + new SelectOption({id: 'false', name: 'Don\'t Rollback'}) + ]; + } +} diff --git a/vid-webpack-master/src/app/components/service-popup/service-popup.component.ts b/vid-webpack-master/src/app/components/service-popup/service-popup.component.ts new file mode 100644 index 00000000..908ae4ad --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-popup.component.ts @@ -0,0 +1,144 @@ +import {Component, ViewChild} from '@angular/core'; +import {DialogComponent, DialogService} from 'ng2-bootstrap-modal'; +import {ServiceModel} from '../../shared/models/serviceModel'; +import {Constants} from '../../shared/utils/constants'; +import {ServiceInstanceDetailsComponent} from './service-instance-details/service-instance-details.component'; +import {ActivatedRoute} from "@angular/router"; +import {AaiService} from "../../services/aaiService/aai.service"; +import {Utils} from "../../utils/utils"; +import {ServicePlanningService} from "../../services/service-planning.service"; +import * as _ from 'lodash'; +import {ModelInformationItem} from '../../shared/components/model-information/model-information.component'; +import {deleteServiceInstance} from '../../service.actions'; + +import {InstancePopup} from "../instance-popup/instance-popup.components"; +import {NgRedux} from "@angular-redux/store"; +import {AppState} from "../../store/reducers"; +import {ServicePopupService} from './service-popup.service'; +import {IframeService} from "../../shared/utils/iframe.service"; + +export interface ServicePopupModel { + serviceModel: ServiceModel +} + +@Component({ + selector: 'service-popup', + templateUrl: 'service-popup.html', + styleUrls: ['service-popup.scss'], + providers: [AaiService, ServicePopupService] +}) + +export class ServicePopupComponent extends DialogComponent<ServicePopupModel, boolean> + implements ServicePopupModel, InstancePopup{ + @ViewChild(ServiceInstanceDetailsComponent) serviceInstanceDetails: ServiceInstanceDetailsComponent; + + serviceModel: ServiceModel; + serviceModelId: string; + serviceInstance: any = { + 'rollbackOnFailure' : 'false' + }; + title: string = Constants.ServicePopup.TITLE; + dynamicInputs: any[] = null; + + maxServiceQty:number = 50; + minServiceQty:number = 1; + servicesQty = 1; //default + quantityOptions = this.getQuantityOptions(); + + modelInformationItems: Array<ModelInformationItem> = []; + hasGeneralApiError : boolean = false; + parentElementClassName = 'content'; + + constructor(dialogService: DialogService, private route: ActivatedRoute, private _aaiService: AaiService, + private _iframeService : IframeService, + private _servicePlanningService: ServicePlanningService, private store: NgRedux<AppState>, private _servicePopupService : ServicePopupService) { + super(dialogService); + this.title = Constants.ServicePopup.TITLE; + } + + updateGeneralErrorSection() : void { + this.hasGeneralApiError = this._servicePopupService.onControlError(this.serviceInstanceDetails, this.serviceInstanceDetails.serviceInstanceDetailsFormGroup); + } + + + ngOnInit() { + this.route + .queryParams + .subscribe(params => { + this.serviceModelId = params['serviceModelId']; + if(params['isCreate']=="true") { + this.store.dispatch(deleteServiceInstance(this.serviceModelId)); + } + this.updateServiceModelById(this.serviceModelId); + this.updateInstanceFromStore(); + }); + } + + updateInstanceFromStore() { + let serviceInstance; + if (_.has(this.store.getState().service.serviceInstance, this.serviceModelId)) { + serviceInstance = Object.assign({}, this.store.getState().service.serviceInstance[this.serviceModelId]); + } + + this.serviceInstance = serviceInstance ? serviceInstance : this.serviceInstance; + this.servicesQty = serviceInstance ? serviceInstance.bulkSize : 1; + if (serviceInstance && serviceInstance.instanceParams && serviceInstance.instanceParams[0]) { + this.dynamicInputs = this.dynamicInputs.map(function (x) { + x.value = (serviceInstance.instanceParams[0][x.id]) ? serviceInstance.instanceParams[0][x.id] : x.value; + return x; + }); + } + } + + updateServiceModelById(serviceModelId) { + this._aaiService.getServiceModelById(serviceModelId).subscribe( + value => { + const convertedModel = Utils.convertModel(value); + this.serviceModel = new ServiceModel(convertedModel); + let displayInputs = Object.assign({},convertedModel.service.inputs); + this.dynamicInputs = _.isEmpty(displayInputs)? [] : this._servicePlanningService.getArbitraryInputs(displayInputs); + this.modelInformationItems = this.createModelInformationItems(); + }, + error => {console.log('error is ', error)}, + () => {console.log('completed')} + ); + } + + createModelInformationItems() : Array<ModelInformationItem> { + return [ + new ModelInformationItem("Model version", "modelVersion", [this.serviceModel.version], "", true), + new ModelInformationItem("Description", "description", [this.serviceModel.description]), + new ModelInformationItem("Category", "category", [this.serviceModel.category]), + new ModelInformationItem("UUID", "uuid", [this.serviceModel.uuid], Constants.ServicePopup.TOOLTIP_UUID, true), + new ModelInformationItem("Invariant UUID", "invariantUuid", [this.serviceModel.invariantUuid], Constants.ServicePopup.TOOLTIP_INVARIANT_UUID, true), + new ModelInformationItem("Service type", "serviceType", [this.serviceModel.serviceType]), + new ModelInformationItem("Service role", "serviceRole", [this.serviceModel.serviceRole]) + ]; + } + + onCancelClick() { + this._iframeService.removeClassCloseModal(this.parentElementClassName); + this.dialogService.removeDialog(this); + this.serviceInstance = this.serviceInstanceDetails.oldServiceInstance; + + this._servicePopupService.resetDynamicInputs(this.serviceInstanceDetails, this.dynamicInputs); + // Delaying the iframe close in few milliseconds. + // This should workaround a problem in Selenium tests' that + // blocks after click because the iframe goes out before + // the driver understands it was clicked. Similar bug is + // described here: + // - https://github.com/mozilla/geckodriver/issues/611 + // - https://bugzilla.mozilla.org/show_bug.cgi?id=1223277 + setTimeout(() => { + window.parent.postMessage("closeIframe", "*"); + }, 15); + } + + getModelName(): string { + return (this.serviceModel && this.serviceModel.name) || ""; + } + + getQuantityOptions(){ + return _.range(this.minServiceQty, this.maxServiceQty + 1); + } +} diff --git a/vid-webpack-master/src/app/components/service-popup/service-popup.html b/vid-webpack-master/src/app/components/service-popup/service-popup.html new file mode 100644 index 00000000..e967daa3 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-popup.html @@ -0,0 +1,52 @@ +<div id="service-popup" class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" (click)="onCancelClick()" >×</button> + <span [attr.data-tests-id]="'create-modal-title'" class="modal-title">{{title}}</span> + </div> + <div class="modal-body popup-content"> + + <div class="header-left"> + <div>SERVICE MODEL: <span>"{{getModelName()}}"</span></div> + </div> + + <div class="header-right"> + Service Instance Details + </div> + + <label class="quantity-label">Qty:</label> + <div class="quantity"> + <select class="quantity-select" [(ngModel)]="servicesQty" name="quantity" id="quantity-select" required> + <option *ngFor="let qty of quantityOptions" [value]="qty">{{qty}}</option> + </select> + </div> + + <div class="service-model"> + + <model-information [modelInformationItems]="modelInformationItems"></model-information> + </div> + + <div class="service-instance"> + <service-instance-details [dynamicInputs]="dynamicInputs" + [serviceInstance]="serviceInstance" + [serviceModel]="serviceModel" + [servicesQty]="servicesQty" + (onDataChanged)="updateGeneralErrorSection()" + (closePopup)="onCancelClick($event)"></service-instance-details> + </div> + + </div> + <div class="modal-footer row" style="padding: 0"> + <div class="col-md-6"> + <div *ngIf="hasGeneralApiError == true"> + <form-general-error [message]="'Page contains errors. Please see details next to the relevant fields.'"></form-general-error> + </div> + </div> + <div class="col-md-6" style="padding: 15px;padding-right: 35px;"> + <button [attr.data-tests-id]="'cancelButton'" type="button" class="btn btn-default cancel" (click)="onCancelClick()"><span>Cancel</span></button> + <input type="submit" value="Set" form="serviceForm" data-tests-id="service-form-set" + class="btn btn-success submit" [disabled]="!serviceInstanceDetails?.serviceForm?.valid"> + </div> + </div> + </div> +</div> diff --git a/vid-webpack-master/src/app/components/service-popup/service-popup.scss b/vid-webpack-master/src/app/components/service-popup/service-popup.scss new file mode 100644 index 00000000..aa4552d2 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-popup.scss @@ -0,0 +1,185 @@ +$grid-border: 1px #d2d2d2 solid; + +#service-popup { + color: #191919; + + .left-panel { + background: #f2f2f2; + border-right: $grid-border; + } + + .header-common { + height: 100%; + align-items: center; + display: flex; + font-family: OpenSans-Semibold; + font-size: 12px; + } + + .header-text { + padding-left: 30px; + @extend .header-common; + } + + .header-left { + grid-area: header-left; + @extend .header-text; + @extend .left-panel; + border-bottom: $grid-border; + + span { + font-family: OpenSans-Regular; + font-size: 14px; + }; + } + + .header-right { + grid-area: header-right; + + @extend .header-text; + border-bottom: $grid-border; + } + + .quantity-label { + grid-area: quantity-label; + @extend .header-common; + border-bottom: $grid-border; + height: 100%; + font-family: OpenSans-Regular; + } + + .quantity { + grid-area: quantity; + border-left: $grid-border; + border-bottom: $grid-border; + border-top-style: none; + font-family: OpenSans-Semibold; + text-align: start; + text-indent: 10px; + } + + .quantity-select { + width: 78px; + height: 100%; + border: 0; + background: white; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: url('../../../assets/img/chevron.svg') 0 0 no-repeat; + background-size: 24px; + background-position-x: right; + background-position-y: center; + } + input[type="number"]:hover::-webkit-inner-spin-button { + height: 20px; + } + + .service-model { + grid-area: service-model; + + padding: 30px; + overflow: auto; + @extend .left-panel; + } + + .service-instance { + grid-area: service-instance; + } + + .popup-content { + display: grid; + grid-template-columns: 400px auto 30px 93px; + grid-template-rows: 50px calc(100vh - 180px); + grid-template-areas: + "header-left header-right quantity-label quantity" + "service-model service-instance service-instance service-instance"; + padding: 0; + } +} + +.modal { + background-color: #191919; + opacity: 0.8; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 0; +} +@media (min-width: 1150px) { + .popup-content { + grid-template-rows: 30px 680px; + } +} + +.modal-content { + border-radius: 0; + box-shadow: none; + border: none; +} + +.modal-footer { + .cancel { + width: 120px; + height: 36px; + background: #ffffff; + border: 1px solid #009fdb; + border-radius: 2px; + span { + font-family: OpenSans-Regular; + font-size: 14px; + color: #009fdb; + line-height: 16px; + } + } + + .submit { + width: 120px; + height: 36px; + background: #009fdb; + border-radius: 2px; + border-color: #009fdb; + span { + font-family: OpenSans-Regular; + font-size: 14px; + color: #FFFFFF; + line-height: 16px; + } + } +} + +.modal-header { + background-color: #009fdb; + + padding-bottom: 13px; + padding-top: 13px; + padding-left: 29px; + padding-right: 21px; + + .close { + font-size: 32px; + font-weight: 200; + color: #d8d8d8; + text-shadow: none; + filter: none; + opacity: 1; + } + + .modal-title { + font-family: OpenSans-Regular; + font-size: 24px; + color: #fff; + line-height: 34px; + } +} +// +//@media (min-width: 1200px) { +// .service-model, +// .service-instance { +// width: 1050px; +// margin: 30px auto; +// } +//} diff --git a/vid-webpack-master/src/app/components/service-popup/service-popup.service.spec.ts b/vid-webpack-master/src/app/components/service-popup/service-popup.service.spec.ts new file mode 100644 index 00000000..cddc6400 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-popup.service.spec.ts @@ -0,0 +1,138 @@ + +import { TestBed, getTestBed} from '@angular/core/testing'; +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import { ServicePopupService } from './service-popup.service'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { NumbersLettersUnderscoreValidator } from '../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; + +describe('Service Popup Service', () => { + let injector; + let service: ServicePopupService; + let httpMock: HttpTestingController; + let form : FormGroup; + let servicePopupDataModel; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ServicePopupService] + }); + + injector = getTestBed(); + service = injector.get(ServicePopupService); + httpMock = injector.get(HttpTestingController); + + form = generateFormGroup(); + servicePopupDataModel = generateServicePopupDataModel(); + }); + + describe('#resetDynamicInputs', () => { + it('resetDynamicInputs should reset dymanic fields',(done: DoneFn) => { + const dynamicInputs = generateDynamicInputs(); + let serviceForm = generateFormGroup(); + serviceForm.addControl(dynamicInputs[0].name, new FormControl({value: dynamicInputs[0].value, disabled: false})); + + serviceForm.controls[dynamicInputs[0].name].setValue("diffValue"); + service.resetDynamicInputs({ + serviceInstanceDetailsFormGroup : serviceForm, + dynamicInputs : dynamicInputs + }, dynamicInputs); + + expect(serviceForm.controls[dynamicInputs[0].name].value).toEqual(dynamicInputs[0].value); + done(); + }) + }); + + describe('#onControlError', () => { + + it('should return true if instanceName is illegal', (done: DoneFn) => { + form.controls['instanceName'].setValue("illegal - illegal"); + + let result : boolean = service.onControlError(<any>servicePopupDataModel, form); + expect(result).toBeTruthy(); + done(); + }); + + it('should return false if instanceName is legal', (done: DoneFn) => { + + form.controls['instanceName'].setValue("legal"); + let result = service.onControlError(<any>servicePopupDataModel, form); + expect(result).toBeFalsy(); + done(); + }); + + it('should return false if lcpRegions is empty and is disabled', (done: DoneFn) => { + servicePopupDataModel.servicePopupDataModel['lcpRegions'] = []; + let result = service.onControlError(<any>servicePopupDataModel, form); + expect(result).toBeFalsy(); + done(); + }); + + it('should return true if lcpRegions is empty', (done: DoneFn) => { + servicePopupDataModel.servicePopupDataModel['lcpRegions'] = []; + form.controls['lcpCloudRegionId'].enable(); + let result = service.onControlError(<any>servicePopupDataModel, form); + expect(result).toBeTruthy(); + done() + }); + }); + + + function generateDynamicInputs(){ + return JSON.parse('[{"id":"2017488_adiodvpe0_ASN","type":"string","name":"2017488_adiodvpe0_ASN","value":"AV_vPE","isRequired":true,"description":"AV/PE"}]'); + } + + + function generateServicePopupDataModel() { + return { + "servicePopupDataModel" : JSON.parse('{"subscribers":[{"id":"a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb","name":"Mobility","isPermitted":false},{"id":"a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fc","name":"PACKET CORE","isPermitted":false},{"id":"e433710f-9217-458d-a79d-1c7aff376d89","name":"USP VOICE","isPermitted":true}],"serviceTypes":[{"id":"0","name":"vFlowLogic","isPermitted":false},{"id":"1","name":"VIRTUAL USP","isPermitted":true},{"id":"2","name":"Mobility","isPermitted":false},{"id":"3","name":"vBNG","isPermitted":false},{"id":"4","name":"vVoiceMail","isPermitted":false},{"id":"5","name":"Nimbus","isPermitted":false},{"id":"6","name":"vSEGW","isPermitted":false},{"id":"7","name":"vVM","isPermitted":false},{"id":"8","name":"vOTA","isPermitted":false},{"id":"9","name":"vMME","isPermitted":false},{"id":"10","name":"vMNS","isPermitted":false},{"id":"11","name":"vSCP","isPermitted":false},{"id":"12","name":"VPMS","isPermitted":false},{"id":"13","name":"vMMSC","isPermitted":false},{"id":"14","name":"SSD","isPermitted":false},{"id":"15","name":"vMOG","isPermitted":false},{"id":"16","name":"FIRSTNET","isPermitted":false},{"id":"17","name":"ACTIVE_CHARGE","isPermitted":false},{"id":"18","name":"vHSS","isPermitted":false}],"aicZones":[{"id":"NFT1","name":"NFTJSSSS-NFT1"},{"id":"JAG1","name":"YUDFJULP-JAG1"},{"id":"YYY1","name":"UUUAIAAI-YYY1"},{"id":"BAN1","name":"VSDKYUTP-BAN1"},{"id":"DKJ1","name":"DKJSJDKA-DKJ1"},{"id":"MCS1","name":"ASACMAMS-MCS1"},{"id":"UIO1","name":"uioclli1-UIO1"},{"id":"RAJ1","name":"YGBIJNLQ-RAJ1"},{"id":"OPA1","name":"opaclli1-OPA1"},{"id":"SDE1","name":"ZXCVBNMA-SDE1"},{"id":"VEN2","name":"FGHJUHIL-VEN2"},{"id":"ORL1","name":"ORLDFLMA-ORL1"},{"id":"JAD1","name":"JADECLLI-JAD1"},{"id":"ZXL1","name":"LWLWCANN-ZXL1"},{"id":"CKL1","name":"CLKSKCKK-CKL1"},{"id":"SDF1","name":"sdfclli1-SDF1"},{"id":"RAD1","name":"RADICAL1-RAD1"},{"id":"KIT1","name":"BHYJFGLN-KIT1"},{"id":"REL1","name":"INGERFGT-REL1"},{"id":"JNL1","name":"CJALSDAC-JNL1"},{"id":"OLK1","name":"OLKOLKLS-OLK1"},{"id":"CHI1","name":"CHILLIWE-CHI1"},{"id":"UUU4","name":"UUUAAAUU-UUU4"},{"id":"TUF1","name":"TUFCLLI1-TUF1"},{"id":"KJN1","name":"CKALDKSA-KJN1"},{"id":"SAM1","name":"SNDGCA64-SAN1"},{"id":"SCK1","name":"SCKSCKSK-SCK1"},{"id":"HJH1","name":"AOEEQQQD-HJH1"},{"id":"HGD1","name":"SDFQWHGD-HGD1"},{"id":"KOR1","name":"HYFLNBVT-KOR1"},{"id":"ATL43","name":"AICLOCID-ATL43"},{"id":"ATL54","name":"AICFTAAI-ATL54"},{"id":"ATL66","name":"CLLIAAII-ATL66"},{"id":"VEL1","name":"BNMLKUIK-VEL1"},{"id":"ICC1","name":"SANJITAT-ICC1"},{"id":"MNT11","name":"WSXEFBTH-MNT11"},{"id":"DEF2","name":"WSBHGTYL-DEF2"},{"id":"MAD11","name":"SDFQWGKL-MAD11"},{"id":"OLG1","name":"OLHOLHOL-OLG1"},{"id":"GAR1","name":"NGFVSJKO-GAR1"},{"id":"SAN22","name":"GNVLSCTL-SAN22"},{"id":"HRG1","name":"HRGHRGGS-HRG1"},{"id":"JCS1","name":"JCSJSCJS-JCS1"},{"id":"DHA12","name":"WSXEDECF-DHA12"},{"id":"HJE1","name":"AOEEWWWD-HJE1"},{"id":"NCA1","name":"NCANCANN-NCA1"},{"id":"IOP1","name":"iopclli1-IOP1"},{"id":"RTY1","name":"rtyclli1-RTY1"},{"id":"KAP1","name":"HIOUYTRQ-KAP1"},{"id":"ZEN1","name":"ZENCLLI1-ZEN1"},{"id":"HKA1","name":"JAKHLASS-HKA1"},{"id":"CQK1","name":"CQKSCAKK-CQK1"},{"id":"SAI1","name":"UBEKQLPD-SAI1"},{"id":"ERT1","name":"ertclli1-ERT1"},{"id":"IBB1","name":"PLMKOIJU-IBB1"},{"id":"TIR2","name":"PLKINHYI-TIR2"},{"id":"HSD1","name":"CHASKCDS-HSD1"},{"id":"SLF78","name":"SDCTLFN1-SLF78"},{"id":"SEE78","name":"SDCTEEE4-SEE78"},{"id":"SAN13","name":"TOKYJPFA-SAN13"},{"id":"SAA78","name":"SDCTAAA1-SAA78"},{"id":"LUC1","name":"ATLDFGYC-LUC1"},{"id":"AMD13","name":"MEMATLAN-AMD13"},{"id":"TOR1","name":"TOROONXN-TOR1"},{"id":"QWE1","name":"QWECLLI1-QWE1"},{"id":"ZOG1","name":"ZOGASTRO-ZOG1"},{"id":"CAL33","name":"CALIFORN-CAL33"},{"id":"SHH78","name":"SDIT1HHH-SHH78"},{"id":"DSA1","name":"LKJHGFDS-DSA1"},{"id":"CLG1","name":"CLGRABAD-CLG1"},{"id":"BNA1","name":"BNARAGBK-BNA1"},{"id":"ATL84","name":"CANTTCOC-ATL84"},{"id":"APP1","name":"WBHGTYUI-APP1"},{"id":"RJN1","name":"RJNRBZAW-RJN1"},{"id":"EHH78","name":"SDCSHHH5-EHH78"},{"id":"mac10","name":"PKGTESTF-mac10"},{"id":"SXB78","name":"SDCTGXB1-SXB78"},{"id":"SAX78","name":"SDCTAXG1-SAX78"},{"id":"SYD1","name":"SYDNAUBV-SYD1"},{"id":"TOK1","name":"TOKYJPFA-TOK1"},{"id":"KGM2","name":"KGMTNC20-KGM2"},{"id":"DCC1b","name":"POIUYTGH-DCC1b"},{"id":"SKK78","name":"SDCTKKK1-SKK78"},{"id":"SGG78","name":"SDCTGGG1-SGG78"},{"id":"SJJ78","name":"SDCTJJJ1-SJJ78"},{"id":"SBX78","name":"SDCTBXG1-SBX78"},{"id":"LAG1","name":"LARGIZON-LAG1"},{"id":"IAA1","name":"QAZXSWED-IAA1"},{"id":"POI1","name":"PLMNJKIU-POI1"},{"id":"LAG1a","name":"LARGIZON-LAG1a"},{"id":"PBL1","name":"PBLAPBAI-PBL1"},{"id":"LAG45","name":"LARGIZON-LAG1a"},{"id":"MAR1","name":"MNBVCXZM-MAR1"},{"id":"HST70","name":"HSTNTX70-HST70"},{"id":"DCC1a","name":"POIUYTGH-DCC1a"},{"id":"TOL1","name":"TOLDOH21-TOL1"},{"id":"LON1","name":"LONEENCO-LON1"},{"id":"SJU78","name":"SDIT1JUB-SJU78"},{"id":"STN27","name":"HSTNTX01-STN27"},{"id":"SSW56","name":"ss8126GT-SSW56"},{"id":"SBB78","name":"SDIT1BBB-SBB78"},{"id":"DCC3","name":"POIUYTGH-DCC3"},{"id":"GNV1","name":"GNVLSCTL-GNV1"},{"id":"WAS1","name":"WASHDCSW-WAS1"},{"id":"TOY1","name":"TORYONNZ-TOY1"},{"id":"STT1","name":"STTLWA02-STT1"},{"id":"STG1","name":"STTGGE62-STG1"},{"id":"SLL78","name":"SDCTLLL1-SLL78"},{"id":"SBU78","name":"SDIT1BUB-SBU78"},{"id":"ATL2","name":"ATLNGANW-ATL2"},{"id":"BOT1","name":"BOTHWAKY-BOT1"},{"id":"SNG1","name":"SNGPSIAU-SNG1"},{"id":"NYC1","name":"NYCMNY54-NYC1"},{"id":"LAG1b","name":"LARGIZON-LAG1b"},{"id":"AMD15","name":"AMDFAA01-AMD15"},{"id":"SNA1","name":"SNANTXCA-SNA1"},{"id":"PLT1","name":"PLTNCA60-PLT1"},{"id":"TLP1","name":"TLPNXM18-TLP1"},{"id":"SDD81","name":"SAIT1DD6-SDD81"},{"id":"DCC1","name":"POIUYTGH-DCC1"},{"id":"DCC2","name":"POIUYTGH-DCC2"},{"id":"OKC1","name":"OKCBOK55-OKC1"},{"id":"PAR1","name":"PARSFRCG-PAR1"},{"id":"TES36","name":"ABCEETES-TES36"},{"id":"COM1","name":"PLMKOPIU-COM1"},{"id":"ANI1","name":"ATLNGTRE-ANI1"},{"id":"SDG78","name":"SDIT1BDG-SDG78"},{"id":"mac20","name":"PKGTESTF-mac20"},{"id":"DSF45","name":"DSFBG123-DSF45"},{"id":"HST25","name":"HSTNTX01-HST25"},{"id":"AMD18","name":"AUDIMA01-AMD18"},{"id":"SAA80","name":"SAIT9AA3-SAA80"},{"id":"SSA56","name":"SSIT2AA7-SSA56"},{"id":"SDD82","name":"SAIT1DD9-SDD82"},{"id":"JCV1","name":"JCVLFLBW-JCV1"},{"id":"SUL2","name":"WERTYUJK-SUL2"},{"id":"PUR1","name":"purelyde-PUR1"},{"id":"FDE55","name":"FDERT555-FDE55"},{"id":"SITE","name":"LONEENCO-SITE"},{"id":"ATL1","name":"ATLNGAMA-ATL1"},{"id":"JUL1","name":"ZXCVBNMM-JUL1"},{"id":"TAT34","name":"TESAAISB-TAT34"},{"id":"XCP12","name":"CHKGH123-XCP12"},{"id":"RAI1","name":"poiuytre-RAI1"},{"id":"HPO1","name":"ATLNGAUP-HPO1"},{"id":"KJF12","name":"KJFDH123-KJF12"},{"id":"SCC80","name":"SAIT9CC3-SCC80"},{"id":"SAA12","name":"SAIT9AF8-SAA12"},{"id":"SAA14","name":"SAIT1AA9-SAA14"},{"id":"ATL35","name":"TTESSAAI-ATL35"},{"id":"CWY1","name":"CWYMOWBS-CWY1"},{"id":"ATL76","name":"TELEPAAI-ATL76"},{"id":"DSL12","name":"DSLFK242-DSL12"},{"id":"ATL53","name":"AAIATLTE-ATL53"},{"id":"SAA11","name":"SAIT9AA2-SAA11"},{"id":"ATL62","name":"TESSASCH-ATL62"},{"id":"AUG1","name":"ASDFGHJK-AUG1"},{"id":"POI22","name":"POIUY123-POI22"},{"id":"SAA13","name":"SAIT1AA9-SAA13"},{"id":"BHY17","name":"BHYTFRF3-BHY17"},{"id":"LIS1","name":"HOSTPROF-LIS1"},{"id":"SIP1","name":"ZXCVBNMK-SIP1"},{"id":"ATL99","name":"TEESTAAI-ATL43"},{"id":"ATL64","name":"FORLOAAJ-ATL64"},{"id":"TAT33","name":"TESAAISA-TAT33"},{"id":"RAD10","name":"INDIPUNE-RAD10"},{"id":"RTW5","name":"BHYTFRY4-RTW5"},{"id":"JGS1","name":"KSJKKKKK-JGS1"},{"id":"ATL98","name":"TEESTAAI-ATL43"},{"id":"WAN1","name":"LEIWANGW-WAN1"},{"id":"ATL44","name":"ATLSANAB-ATL44"},{"id":"RTD2","name":"BHYTFRk4-RTD2"},{"id":"NIR1","name":"ORFLMANA-NIR1"},{"id":"ATL75","name":"SANAAIRE-ATL75"},{"id":"NUM1","name":"QWERTYUI-NUM1"},{"id":"MTN32","name":"MDTWNJ21-MTN32"},{"id":"RTZ4","name":"BHYTFRZ6-RTZ4"},{"id":"ATL56","name":"ATLSANAC-ATL56"},{"id":"AMS1","name":"AMSTNLBW-AMS1"},{"id":"RCT1","name":"AMSTERNL-RCT1"},{"id":"JAN1","name":"ORFLMATT-JAN1"},{"id":"ABC14","name":"TESAAISA-ABC14"},{"id":"TAT37","name":"TESAAISD-TAT37"},{"id":"MIC54","name":"MICHIGAN-MIC54"},{"id":"ABC11","name":"ATLSANAI-ABC11"},{"id":"AMF11","name":"AMDOCS01-AMF11"},{"id":"ATL63","name":"ATLSANEW-ATL63"},{"id":"ABC12","name":"ATLSECIA-ABC12"},{"id":"MTN20","name":"MDTWNJ21-MTN20"},{"id":"ABC15","name":"AAITESAN-ABC15"},{"id":"AVT1","name":"AVTRFLHD-AVT1"},{"id":"ATL34","name":"ATLSANAI-ATL34"}],"lcpRegions":[{"id":"AAIAIC25","name":"AAIAIC25","isPermitted":true},{"id":"mtn6","name":"mtn6","isPermitted":true}],"lcpRegionsTenantsMap":{},"tenants":[{"id":"bae71557c5bb4d5aac6743a4e5f1d054","name":"AIN Web Tool-15-D-testgamma","isPermitted":true},{"id":"229bcdc6eaeb4ca59d55221141d01f8e","name":"AIN Web Tool-15-D-STTest2","isPermitted":true},{"id":"1178612d2b394be4834ad77f567c0af2","name":"AIN Web Tool-15-D-SSPtestcustome","isPermitted":true},{"id":"19c5ade915eb461e8af52fb2fd8cd1f2","name":"AIN Web Tool-15-D-UncheckedEcopm","isPermitted":true},{"id":"de007636e25249238447264a988a927b","name":"AIN Web Tool-15-D-dfsdf","isPermitted":true},{"id":"62f29b3613634ca6a3065cbe0e020c44","name":"AIN/SMS-16-D-Multiservices1","isPermitted":true},{"id":"649289e30d3244e0b48098114d63c2aa","name":"AIN Web Tool-15-D-SSPST66","isPermitted":true},{"id":"3f21eeea6c2c486bba31dab816c05a32","name":"AIN Web Tool-15-D-ASSPST47","isPermitted":true},{"id":"f60ce21d3ee6427586cff0d22b03b773","name":"CESAR-100-D-sspjg67246","isPermitted":true},{"id":"8774659e425f479895ae091bb5d46560","name":"CESAR-100-D-sspjg68359","isPermitted":true},{"id":"624eb554b0d147c19ff8885341760481","name":"AINWebTool-15-D-iftach","isPermitted":true},{"id":"214f55f5fc414c678059c383b03e4962","name":"CESAR-100-D-sspjg612401","isPermitted":true},{"id":"c90666c291664841bb98e4d981ff1db5","name":"CESAR-100-D-sspjg621340","isPermitted":true},{"id":"ce5b6bc5c7b348e1bf4b91ac9a174278","name":"sspjg621351cloned","isPermitted":true},{"id":"b386b768a3f24c8e953abbe0b3488c02","name":"AINWebTool-15-D-eteancomp","isPermitted":true},{"id":"dc6c4dbfd225474e9deaadd34968646c","name":"AINWebTool-15-T-SPFET","isPermitted":true},{"id":"02cb5030e9914aa4be120bd9ed1e19eb","name":"AINWebTool-15-X-eeweww","isPermitted":true},{"id":"f2f3830e4c984d45bcd00e1a04158a79","name":"CESAR-100-D-spjg61909","isPermitted":true},{"id":"05b91bd5137f4929878edd965755c06d","name":"CESAR-100-D-sspjg621512cloned","isPermitted":true},{"id":"7002fbe8482d4a989ddf445b1ce336e0","name":"AINWebTool-15-X-vdr","isPermitted":true},{"id":"4008522be43741dcb1f5422022a2aa0b","name":"AINWebTool-15-D-ssasa","isPermitted":true},{"id":"f44e2e96a1b6476abfda2fa407b00169","name":"AINWebTool-15-D-PFNPT","isPermitted":true},{"id":"b69a52bec8a84669a37a1e8b72708be7","name":"AINWebTool-15-X-vdre","isPermitted":true},{"id":"fac7d9fd56154caeb9332202dcf2969f","name":"AINWebTool-15-X-NONPODECOMP","isPermitted":true},{"id":"2d34d8396e194eb49969fd61ffbff961","name":"DN5242-Nov16-T5","isPermitted":true},{"id":"cb42a77ff45b48a8b8deb83bb64acc74","name":"ro-T11","isPermitted":true},{"id":"fa45ca53c80b492fa8be5477cd84fc2b","name":"ro-T112","isPermitted":true},{"id":"4914ab0ab3a743e58f0eefdacc1dde77","name":"DN5242-Nov21-T1","isPermitted":true},{"id":"d0a3e3f2964542259d155a81c41aadc3","name":"test-mtn6-09","isPermitted":true},{"id":"cbb99fe4ada84631b7baf046b6fd2044","name":"DN5242-Nov16-T3","isPermitted":true}],"productFamilies":null,"projects":[{"id":"DFW","name":"DFW"},{"id":"x1","name":"x1"},{"id":"yyy1","name":"yyy1"}],"owningEntities":[{"id":"aaa1","name":"aaa1"},{"id":"d61e6f2d-12fa-4cc2-91df-7c244011d6fc","name":"MetroPacketCore"},{"id":"Wireline","name":"Wireline"}],"globalCustomerId":"e433710f-9217-458d-a79d-1c7aff376d89"}')} + } + + function generateFormGroup(){ + return new FormGroup({ + globalSubscriberId: new FormControl( + Validators.compose([Validators.required]) + ), + productFamilyId: new FormControl(), + subscriptionServiceType: new FormControl({value: null, disabled: true}, Validators.compose([Validators.required])), + lcpCloudRegionId: new FormControl({value: null, disabled: true}, Validators.compose([Validators.required])), + tenantId: new FormControl({value: null, disabled: true}, Validators.compose([Validators.required])), + aicZoneId: new FormControl(), + projectName: new FormControl(), + owningEntityId: new FormControl(Validators.compose([Validators.required])), + instanceName : new FormControl({value: null}, Validators.compose([Validators.required, NumbersLettersUnderscoreValidator.valid])) + }); + } + + + function generateServiceInstanceDetails(){ + return { + servicePopupDataModel : { + "productFamilies" : [] + }, + serviceInstanceDetailsFormGroup : { + controls : { + productFamilyId : { + disabled : false + } + } + } + } + } + + function generateLegalServiceInstance(){ + return { + instanceName : "legalInstanceName" + } + } + + function generateIllegalServiceInstance(){ + return { + instanceName : "illegalInstanceName" + } + } + +}); diff --git a/vid-webpack-master/src/app/components/service-popup/service-popup.service.ts b/vid-webpack-master/src/app/components/service-popup/service-popup.service.ts new file mode 100644 index 00000000..f6efd353 --- /dev/null +++ b/vid-webpack-master/src/app/components/service-popup/service-popup.service.ts @@ -0,0 +1,33 @@ +import {Injectable} from '@angular/core'; +import {isNullOrUndefined} from "util"; +import {NumbersLettersUnderscoreValidator} from '../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; +import {ServiceInstanceDetailsComponent} from './service-instance-details/service-instance-details.component'; +import {FormGroup} from '@angular/forms'; +import * as _ from "lodash"; + +@Injectable() +export class ServicePopupService { + onControlError(serviceInstanceDetails : ServiceInstanceDetailsComponent, serviceInstanceDetailsFormGroup : FormGroup) : boolean{ + if(!isNullOrUndefined(serviceInstanceDetailsFormGroup) && !isNullOrUndefined(serviceInstanceDetailsFormGroup.controls['instanceName']) && NumbersLettersUnderscoreValidator.valid(serviceInstanceDetailsFormGroup.controls['instanceName'].value) && serviceInstanceDetailsFormGroup.controls['instanceName'].value != null && serviceInstanceDetailsFormGroup.controls['instanceName'].value.length > 0){ + return true; + } + + const controlName : Array<string> = ['productFamilyId', 'lcpCloudRegionId', 'tenantId', 'owningEntityId', 'projectName', 'aicZoneId', 'subscriptionServiceType', 'globalSubscriberId', 'rollbackOnFailure']; + const selectDataName : Array<string> = ['productFamilies', 'lcpRegions', 'tenants', 'owningEntities', 'projects', 'aicZones', 'serviceTypes', 'subscribers', 'rollbackOnFailure']; + for(let i = 0 ; i < controlName.length ; i++){ + if (!isNullOrUndefined(serviceInstanceDetails.servicePopupDataModel) && !isNullOrUndefined(serviceInstanceDetails.servicePopupDataModel[selectDataName[i]])) { + if (!serviceInstanceDetailsFormGroup.controls[controlName[i]].disabled && serviceInstanceDetails.servicePopupDataModel[selectDataName[i]].length === 0) { + return true; + } + } + } + return false; + } + + resetDynamicInputs(serviceInstance : any, defaultDynamicInputs : any) : void { + for(let dynamicInput of serviceInstance.dynamicInputs){ + const defaultDymanicInput = _.find(defaultDynamicInputs, {name:dynamicInput.name}); + serviceInstance.serviceInstanceDetailsFormGroup.controls[dynamicInput.name].setValue(defaultDymanicInput.value); + } + } +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.component.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.component.ts new file mode 100644 index 00000000..725e4429 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.component.ts @@ -0,0 +1,275 @@ +import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {VNFPopupDataModel} from './vnfPopupDataModel'; +import {AaiService} from '../../../services/aaiService/aai.service'; +import { createVFModuleInstance, updateVFModuleInstance, updateVNFInstance } from '../../../service.actions'; +import {VnfInstance} from "../../../shared/models/vnfInstance"; +import {ServiceInstance} from "../../../shared/models/serviceInstance"; +import {VNFModel} from "../../../shared/models/vnfModel"; +import {InputType} from "../../../shared/models/inputTypes"; +import {ModelInfo} from "../../../shared/models/modelInfo"; +import {VfModuleInstance} from "../../../shared/models/vfModuleInstance"; +import {NgRedux, select} from "@angular-redux/store"; +import {AppState} from "../../../store/reducers"; +import {SelectOptionInterface} from "../../../shared/models/selectOption"; +import {Observable} from "rxjs/Observable"; +import {loadProductFamiliesAction} from "../../../services/aaiService/aai.actions"; +import {VnfInstanceDetailsService} from "./vnf-instance-details.service"; +import {isNullOrUndefined} from 'util'; +import {NumbersLettersUnderscoreValidator} from '../../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; +import * as _ from "lodash"; +import {ServiceNodeTypes} from "../../../shared/models/ServiceNodeTypes"; + +@Component({ + selector: 'vnf-instance-details', + templateUrl: 'vnf-instance-details.html', + styleUrls: ['vnf-instance-details.scss'], + providers: [AaiService] +}) + +export class VnfInstanceDetailsComponent implements OnInit { + @ViewChild('vnfForm') vnfForm: 'VnfForm'; + _vnfModel: VNFModel; + @Input () + set vnfModel(vnfModel: VNFModel) { + this._vnfModel = vnfModel; + this.updateFormGroupControlsFromVNFModel(); + } + @Input() vnfInstance: any; + @Input() serviceInstance: ServiceInstance; + @Input() dynamicInputs; + @Input() modelName: string; + @Input() serviceUuid: string; + @Input() userProvidedNaming: boolean; + _modelType: string; + @Input() + set modelType(modelType: string) { + this._modelType = modelType; + this.updateFormGroupControlsFromVNFModel(); + } + + @Input() parentModelName: string; + @Input() isNewVfModule : boolean; + + + @Output() onSubmitClick: EventEmitter<any> = new EventEmitter<any>(); + @Output() onServiceInstanceNameChanged : EventEmitter<boolean> = new EventEmitter<boolean>(); + @Output() onVolumeGroupNameChanged : EventEmitter<boolean> = new EventEmitter<boolean>(); + +@Output() onDataChanged: EventEmitter<any> = new EventEmitter<any>(); + @select(['service','productFamilies']) + readonly productFamilies : Observable<SelectOptionInterface[]>; + + vnfPopupDataModel: VNFPopupDataModel = new VNFPopupDataModel(); + lcpRegionsThatEnableLegacyRegionField = ['AAIAIC25', 'rdm3', 'rdm5a']; + shouldShowLegacyRegion: boolean; + instanceFormGroup: FormGroup = null; + inputType = InputType; + isNotUniqueInstanceName : boolean = false; + isNotUniqueVolumeGroupName : boolean = false; + + constructor(private _aaiService: AaiService, private store: NgRedux<AppState>, + private _vnfInstanceDetailsService : VnfInstanceDetailsService) { + this.store.subscribe(() => { + this.updateFormData() + }); + } + + ngOnInit() { + this.updateFormGroup(); + this.subscribeToFormChanges(); + this._aaiService.getCategoryParameters(null).subscribe(); + this._aaiService.getLcpRegionsAndTenants(this.serviceInstance.globalSubscriberId, this.serviceInstance.subscriptionServiceType).subscribe(); + this.updateLegacyRegionVisibility(); + this.store.dispatch(loadProductFamiliesAction()); + } + + isInputShouldBeShown(inputType: any) { + let vnfInputs = [InputType.LCP_REGION, InputType.LOB, InputType.TENANT, InputType.PRODUCT_FAMILY, InputType.PLATFORM, InputType.ROLLBACK]; + let vfInputs = [InputType.VG]; + let exist = false; + if (this._modelType === 'VF') { + exist = vnfInputs.indexOf(inputType) > -1; + } + else { + exist = vfInputs.indexOf(inputType) > -1; + } + return exist; + } + + updateFormGroupControlsFromVNFModel() { + if (this._vnfModel && this._modelType) { + if (this._modelType === ServiceNodeTypes.VF) { + const vnfInstance = <VnfInstance>this.vnfInstance; + if (this.instanceFormGroup && this.userProvidedNaming + && !this.instanceFormGroup.get('instanceName')) { + const initialInstanceName = vnfInstance.instanceName || (!isNullOrUndefined(this._vnfModel.name) ? this._vnfModel.name.replace(/[-]/g, "") : this._vnfModel.name); + this.instanceFormGroup.addControl('instanceName', new FormControl(initialInstanceName, Validators.compose([Validators.required, NumbersLettersUnderscoreValidator.valid]))) + } + } + else if (this._modelType === ServiceNodeTypes.VFmodule) { + const vfInstance = <VfModuleInstance>this.vnfInstance; + if (this.instanceFormGroup && this.userProvidedNaming && !this.instanceFormGroup.get('instanceName')) { + this.instanceFormGroup.addControl('instanceName', new FormControl(vfInstance.instanceName, Validators.required)); + + let vfModule = this.extractVfAccordingToVfModuleUuid(this.store.getState(), this._vnfModel.uuid); + if (vfModule.volumeGroupAllowed && !this.instanceFormGroup.get('volumeGroupName')) { + this.instanceFormGroup.addControl('volumeGroupName', new FormControl(vfInstance.volumeGroupName)); + } + } + } + } + } + + updateFormGroup() { + const tenantDisabled = !this.vnfInstance.lcpCloudRegionId; + + if (this._modelType === ServiceNodeTypes.VF) { + const vnfInstance = <VnfInstance>this.vnfInstance; + this.instanceFormGroup = new FormGroup({ + productFamilyId: new FormControl(vnfInstance.productFamilyId), + lcpCloudRegionId: new FormControl(vnfInstance.lcpCloudRegionId, Validators.required), + tenantId: new FormControl({value: vnfInstance.tenantId, disabled: tenantDisabled}, Validators.required), + legacyRegion: new FormControl(vnfInstance.legacyRegion), + lineOfBusiness: new FormControl(vnfInstance.lineOfBusiness), + platformName: new FormControl(vnfInstance.platformName, Validators.required), + }); + } + else if (this._modelType === ServiceNodeTypes.VFmodule) { + const vfInstance = <VfModuleInstance>this.vnfInstance; + this.instanceFormGroup = new FormGroup({ + }); + } + + this.instanceFormGroup.valueChanges.subscribe(()=> { + this.checkForUniqueInstanceName(); + this.onDataChanged.next(); + }); + + this.updateFormGroupControlsFromVNFModel(); + } + + private getParentVnfModel(): VNFModel { + const rawModel = _.get(this.store.getState().service.serviceHierarchy[this.serviceUuid], ['vnfs', this.parentModelName]); + return new VNFModel(rawModel); + } + + extractVfAccordingToVfModuleUuid(state : any,vfModuleUuid : string) { + const vnfs = this.store.getState().service.serviceHierarchy[this.serviceUuid].vnfs; + const vnfsArray = Object.values(vnfs); + for (let i = 0; i<vnfsArray.length;i++){ + let vfModules = Object.values(vnfsArray[i].vfModules); + for (let j = 0; j<vfModules.length;j++){ + if (vfModules[j].uuid === vfModuleUuid){ + return vfModules[j]; + } + } + } + } + + updateFormData() { + let service = this.store.getState().service; + this.vnfPopupDataModel.lcpRegions = service.lcpRegionsAndTenants.lcpRegionList; + if (this.vnfInstance && this.vnfInstance.lcpCloudRegionId) { + this.vnfPopupDataModel.tenants = service.lcpRegionsAndTenants.lcpRegionsTenantsMap[this.vnfInstance.lcpCloudRegionId]; + console.log('setting vnf instances tenant: ' + JSON.stringify(this.vnfPopupDataModel.tenants)); + } + this.vnfPopupDataModel.platforms = service.categoryParameters.platformList; + this.vnfPopupDataModel.lineOfBusinesses = service.categoryParameters.lineOfBusinessList; + this.onDataChanged.next(); + } + + subscribeToFormChanges(): void { + if (this.instanceFormGroup.get('lcpCloudRegionId') !== null) { + this.instanceFormGroup.get('lcpCloudRegionId').valueChanges.subscribe(val => { + this.setDisabledState(val, 'tenantId'); + this.updateTenantList(val); + this.updateLegacyRegionVisibility(); + this.onDataChanged.next(); + }); + } + } + + setDisabledState(val, field: string): void { + if (val) { + this.instanceFormGroup.controls[field].enable(); + } + } + + updateLegacyRegionVisibility() { + if (this.instanceFormGroup.get('lcpCloudRegionId') !== null) { + this.shouldShowLegacyRegion = this.lcpRegionsThatEnableLegacyRegionField.indexOf(this.instanceFormGroup.get('lcpCloudRegionId').value) > -1; + if (!this.shouldShowLegacyRegion) { + this.instanceFormGroup.controls.legacyRegion.setValue(undefined); + } + } + } + + updateTenantList(cloudRegionId) { + this.resetTenantSelection(); + const tenantsForCloudRegionId = this.store.getState().service.lcpRegionsAndTenants.lcpRegionsTenantsMap[cloudRegionId]; + console.log('tenants for selected cloud region id: ' + JSON.stringify(tenantsForCloudRegionId)); + this.vnfPopupDataModel.tenants = tenantsForCloudRegionId; + } + + resetTenantSelection() { + this.instanceFormGroup.controls.tenantId.setValue(undefined); + } + + checkForUniqueInstanceName() { + let currentName = !isNullOrUndefined(this.instanceFormGroup.get('instanceName')) ? this.instanceFormGroup.get('instanceName').value : null; + + if(currentName && !this._vnfInstanceDetailsService.isUnique(this.store.getState().service.serviceInstance, this.serviceUuid, currentName, currentName === this.serviceInstance.instanceName) && this.userProvidedNaming){ + this.isNotUniqueInstanceName = true; + this.onServiceInstanceNameChanged.emit(true); + }else { + this.isNotUniqueInstanceName = false; + this.onServiceInstanceNameChanged.emit(false); + } + } + + checkForUniqueGroupName(){ + let currentName = this.instanceFormGroup.get('volumeGroupName').value; + if( !this._vnfInstanceDetailsService.isUnique(this.store.getState().service.serviceInstance, this.serviceUuid, currentName, currentName === this.serviceInstance['volumeGroupName'])){ + this.isNotUniqueVolumeGroupName = true; + this.onVolumeGroupNameChanged.emit(true); + }else { + this.isNotUniqueVolumeGroupName = false; + this.onVolumeGroupNameChanged.emit(false); + } + } + + onSubmit(formValues): void { + formValues.modelInfo = new ModelInfo(this._vnfModel); + if (this._modelType === 'VFmodule') { + let dynamicFields: { [dynamicField: string]: string; }; + dynamicFields = {}; + if(!_.isEmpty(this.dynamicInputs)) { + this.dynamicInputs.map(function (x) { + let dynamicField: string = x.id; + dynamicFields[dynamicField] = formValues[dynamicField]; + delete formValues[dynamicField]; + }); + } + formValues.instanceParams = []; + formValues.instanceParams.push(dynamicFields); + if(this.isNewVfModule){ + this.store.dispatch(createVFModuleInstance(formValues, this.modelName, this.serviceUuid)); + }else { + this.store.dispatch(updateVFModuleInstance(formValues, this.modelName, this.serviceUuid)); + } + + } + else { + formValues.isUserProvidedNaming = this.userProvidedNaming; + this.store.dispatch(updateVNFInstance(formValues, this.modelName, this.serviceUuid)); + } + window.parent.postMessage({ + eventId: 'submitIframe', + data: { + serviceModelId: this.serviceUuid + } + }, "*"); + this.onSubmitClick.emit(this.serviceUuid); + } +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.html b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.html new file mode 100644 index 00000000..ccdaac53 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.html @@ -0,0 +1,114 @@ + +<div id="vnf-instance-details"> + <form id="vnfForm" #vnfForm="ngForm" (ngSubmit)="onSubmit(vnfForm.value)" [formGroup]="instanceFormGroup"> + + <div class="details-item" *ngIf="instanceFormGroup.get('instanceName')"> + <label class="required">Instance name:</label> + <input patternInput + pattern="^[a-zA-Z0-9_]*$" + [attr.data-tests-id]="'instanceName'" + [ngClass]="{'error-style' : _vnfInstanceDetailsService.hasInstanceNameError(instanceFormGroup) || _vnfInstanceDetailsService.hasUniqueError(isNotUniqueInstanceName)}" + id="instance-name" name="instance-name" + [formControlName]="'instanceName'" + class="form-control input-text" + placeholder="Type Instance Name" + type="text" + (blur)="checkForUniqueInstanceName()"> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasUniqueError(isNotUniqueInstanceName)" [message]="'Instance name is already in use, please pick another name.'"></form-control-error> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasInstanceNameError(instanceFormGroup)" [message]="'Instance name may include only alphanumeric characters and underscore.'"></form-control-error> + </div> + + <div *ngIf="isInputShouldBeShown(inputType.PRODUCT_FAMILY)" class="details-item"> + <label>Product family:</label> + <select class="form-control input-text" + [ngClass]="{'error-style' :_vnfInstanceDetailsService.hasApiError('productFamilyId',productFamilies, instanceFormGroup)}" + data-tests-id="productFamily" + id="product-family-select" + [formControlName]="'productFamilyId'" + name="product-family-select" > + <option [value]="null" disabled>Select Product Family</option> + <option *ngFor="let productFamily of productFamilies | async" [value]="productFamily.id" + [disabled]="!productFamily.isPermitted">{{productFamily.name}}</option> + </select> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasApiError('productFamilyId',productFamilies, instanceFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div *ngIf="isInputShouldBeShown(inputType.LCP_REGION)" class="details-item"> + <label class="required">LCP region:</label> + <select + [ngClass]="{'error-style' :_vnfInstanceDetailsService.hasApiError('lcpCloudRegionId',vnfPopupDataModel?.lcpRegions, instanceFormGroup)}" + class="form-control input-text" + [formControlName]="'lcpCloudRegionId'" + name="lcpRegion" id="lcpRegion-select" + data-tests-id="lcpRegion"> + <option [value]="null" disabled>Select LCP Region</option> + <option *ngFor="let lcpRegion of vnfPopupDataModel.lcpRegions" [value]="lcpRegion.id" [disabled]="!lcpRegion.isPermitted" class="lcpRegionOption">{{lcpRegion.id}}</option> + </select> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasApiError('lcpCloudRegionId',vnfPopupDataModel?.lcpRegions, instanceFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div class="details-item" *ngIf="shouldShowLegacyRegion"> + <label>Legacy Region:</label> + <input + [attr.data-tests-id]="'lcpRegionText'" + id="legacy-region" + name="legacy-region" + [formControlName]="'legacyRegion'" + class="form-control input-text" + placeholder="Type Legacy Region" type="text"> + </div> + + <div *ngIf="isInputShouldBeShown(inputType.TENANT)" class="details-item"> + <label class="required">Tenant:</label> + <select class="form-control input-text" + [ngClass]="{'error-style' :_vnfInstanceDetailsService.hasApiError('tenantId',vnfPopupDataModel?.tenants, instanceFormGroup)}" + [formControlName]="'tenantId'" + name="tenant" id="tenant-select" data-tests-id="tenant"> + <option [value]="undefined" disabled>Select Tenant</option> + <option *ngFor="let tenant of vnfPopupDataModel.tenants" [value]="tenant.id" [disabled]="!tenant.isPermitted">{{tenant.name}}</option> + </select> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasApiError('tenantId',vnfPopupDataModel?.tenants, instanceFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div *ngIf="isInputShouldBeShown(inputType.LOB)" class="details-item"> + <label>Line of business:</label> + <select [attr.data-tests-id]="'lineOfBusiness'" + class="form-control input-text" + [ngClass]="{'error-style' :_vnfInstanceDetailsService.hasApiError('lineOfBusiness',vnfPopupDataModel?.lineOfBusinesses, instanceFormGroup)}" + name="lineOfBusiness" id="lineOfBusiness" + [formControlName]="'lineOfBusiness'"> + <option [value]="null" disabled>Select Line Of Business</option> + <option *ngFor="let project of vnfPopupDataModel.lineOfBusinesses" [value]="project.id">{{project.name}}</option> + </select> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasApiError('lineOfBusiness',vnfPopupDataModel?.lineOfBusinesses, instanceFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + <div *ngIf="isInputShouldBeShown(inputType.PLATFORM)" class="details-item"> + <label class="required">Platform:</label> + <select + [attr.data-tests-id]="'platform'" + [ngClass]="{'error-style' :_vnfInstanceDetailsService.hasApiError('platformName',vnfPopupDataModel?.platforms, instanceFormGroup)}" + class="form-control input-text" + [formControlName]="'platformName'" + name="platform" id="platform"> + <option [value]="null" disabled>Select Platform</option> + <option *ngFor="let platform of vnfPopupDataModel.platforms" [value]="platform.id">{{platform.name}}</option> + </select> + <form-control-error *ngIf="_vnfInstanceDetailsService.hasApiError('platformName',vnfPopupDataModel?.platforms, instanceFormGroup)" [message]="'No results for this request. Please change criteria.'"></form-control-error> + </div> + + + <div *ngIf="isInputShouldBeShown(inputType.VG) && instanceFormGroup.get('volumeGroupName')" class="details-item" > + <label class="required">Volume Group Name:</label> + <input [attr.data-tests-id]="'volumeGroupName'" + id="vgName" name="vgName" + [ngClass]="{'error-style' :isNotUniqueVolumeGroupName}" + [formControlName]="'volumeGroupName'" + class="form-control input-text" + placeholder="Type Instance Name" type="text" (blur)="checkForUniqueGroupName()"> + <form-control-error *ngIf="isNotUniqueVolumeGroupName" [message]="'Volume Group instance name is already in use, please pick another name.'"></form-control-error> + </div> + + <dynamic-inputs *ngIf="dynamicInputs != undefined && dynamicInputs.length>0" [group]="instanceFormGroup" [list]="dynamicInputs"></dynamic-inputs> + </form> +</div> diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.scss b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.scss new file mode 100644 index 00000000..080540a5 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.scss @@ -0,0 +1,64 @@ +#vnf-instance-details { + position: relative; + + #notification-area { + color: #959595; + font-size: 12px; + position: absolute; + top: 3px; + left: 30px; + } + + height: 100%; + overflow: auto; + padding: 30px; + + /deep/ { + .form-control { + border-radius: 2px; + box-shadow: none; + border-color: #D2D2D2; + } + + label { + font-family: OpenSans-Semibold; + font-size: 12px; + } + + select { + @extend .form-control; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: url('../../../../assets/img/chevron.svg') 0 0 no-repeat; + background-size: 24px; + background-position-x: right; + background-position-y: center; + font-family: OpenSans-Italic; + font-size: 14px; + color: #959595; + height: 38px; + } + + input:not([type='checkbox']) { + @extend .form-control; + height: 38px; + } + + .form-control[disabled], fieldset[disabled] .form-control { + opacity: 0.5; + } + .input-text { + border: 1px solid #D2D2D2; + border-radius: 2px; + } + + .details-item { + margin-bottom: 20px; + } + } + + .checkbox-label { + font-family: OpenSans-Regular; + } +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.service.spec.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.service.spec.ts new file mode 100644 index 00000000..41ddb437 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.service.spec.ts @@ -0,0 +1,241 @@ +import { TestBed, getTestBed } from '@angular/core/testing'; +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import { VnfInstanceDetailsService } from './vnf-instance-details.service'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { NumbersLettersUnderscoreValidator } from '../../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; + +describe('Vnf Instance Details Service', () => { + let injector; + let service: VnfInstanceDetailsService; + let httpMock: HttpTestingController; + + let SERVICE_ID: string = '1a80c596-27e5-4ca9-b5bb-e03a7fd4c0fd'; + let serviceHierarchy; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [VnfInstanceDetailsService] + }); + + injector = getTestBed(); + service = injector.get(VnfInstanceDetailsService); + httpMock = injector.get(HttpTestingController); + serviceHierarchy = getServiceServiceHierarchy(); + }); + + + describe('#hasInstanceNameError', ()=> { + it('hasInstanceNameError should return true if instanceName is illegal and enabled', (done: DoneFn) => { + let form = generateFormGroup(); + form.controls['instanceName'].setValue('----'); + form.controls['instanceName'].setErrors({ + pattern : true + }); + form.controls['instanceName'].markAsTouched(); + let result = service.hasInstanceNameError(form); + expect(result).toBeTruthy(); + done(); + }); + + it('hasInstanceNameError should return false if instanceName is illegal and enabled and pattern is ok', (done: DoneFn) => { + let form = generateFormGroup(); + form.controls['instanceName'].setValue('----'); + form.controls['instanceName'].setErrors({ + otherError : true + }); + form.controls['instanceName'].markAsTouched(); + let result = service.hasInstanceNameError(form); + expect(result).toBeFalsy(); + done(); + }); + }); + + describe('#isUnique', () => { + it('Create Mode: should return false if instanceName exist', (done: DoneFn) => { + serviceHierarchy = getServiceServiceHierarchy(); + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'uniqueInstanceName', false); + expect(result).toBeFalsy(); + done(); + }); + + it('Update Mode: should return true if instanceName exist once', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'uniqueInstanceName', true); + expect(result).toBeTruthy() + done(); + }); + + it('Create Mode: should return true if instanceName not exist', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'uniqueInstanceNameNotExist', false); + expect(result).toBeTruthy(); + done(); + }); + + it('Create Mode: should return false if instanceName exist inside vf modules', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'uniqueInstanceNameVfModule', false); + expect(result).toBeFalsy(); + done(); + }); + + it('Update Mode: should return true if instanceName exist once inside vf modules', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'uniqueInstanceNameVfModule', true); + expect(result).toBeTruthy(); + done(); + }); + + it('Create Mode: should return true if instanceName is not exist at vf modules and vnfs', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'uniqueInstanceNameVfModuleNotExist', false); + expect(result).toBeTruthy(); + done(); + }); + + it('Create Mode: should return false if instanceName exist service name', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'Instance-Name', false); + expect(result).toBeFalsy(); + done(); + }); + + it('Create Mode: should return false if volumeGroupName exist service name', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'volumeGroupNameExist', false); + expect(result).toBeFalsy(); + done(); + }); + + it('Create Mode: should return true if volumeGroupName not exist service name', (done: DoneFn) => { + let result = service.isUnique(serviceHierarchy, SERVICE_ID, 'volumeGroupNameNotExist', false); + expect(result).toBeTruthy(); + done(); + }); + }); + + function getServiceServiceHierarchy() { + return JSON.parse(JSON.stringify( + { + "1a80c596-27e5-4ca9-b5bb-e03a7fd4c0fd": { + "vnfs": { + "2017-388_ADIOD-vPE 1": { + "rollbackOnFailure": "true", + "vfModules": {}, + "instanceParams": [ + {} + ], + "productFamilyId": "a4f6f2ae-9bf5-4ed7-b904-06b2099c4bd7", + "lcpCloudRegionId": "AAIAIC25", + "tenantId": "092eb9e8e4b7412e8787dd091bc58e86", + "lineOfBusiness": "zzz1", + "platformName": "platform", + "instanceName": "uniqueInstanceName", + "modelInfo": { + "modelInvariantId": "00beb8f9-6d39-452f-816d-c709b9cbb87d", + "modelVersionId": "0903e1c0-8e03-4936-b5c2-260653b96413", + "modelName": "2017-388_ADIOD-vPE", + "modelVersion": "1.0", + "modelCustomizationId": "280dec31-f16d-488b-9668-4aae55d6648a", + "modelCustomizationName": "2017-388_ADIOD-vPE 1" + }, + "isUserProvidedNaming": true + }, + "2017-388_ADIOD-vPE 0": { + "rollbackOnFailure": "true", + "vfModules": {}, + "instanceParams": [ + {} + ], + "productFamilyId": null, + "lcpCloudRegionId": "mtn6", + "tenantId": "1178612d2b394be4834ad77f567c0af2", + "lineOfBusiness": "ECOMP", + "platformName": "xxx1", + "instanceName": "blaaa", + "modelInfo": { + "modelInvariantId": "72e465fe-71b1-4e7b-b5ed-9496118ff7a8", + "modelVersionId": "afacccf6-397d-45d6-b5ae-94c39734b168", + "modelName": "2017-388_ADIOD-vPE", + "modelVersion": "4.0", + "modelCustomizationId": "b3c76f73-eeb5-4fb6-9d31-72a889f1811c", + "modelCustomizationName": "2017-388_ADIOD-vPE 0" + }, + "isUserProvidedNaming": true + }, + "2017488_ADIODvPE 0": { + "rollbackOnFailure": "true", + "vfModules": { + "2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vRE_BV..module-1": { + "2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vRE_BV..module-1": { + "rollbackOnFailure": "true", + "instanceName": "uniqueInstanceNameVfModule", + "volumeGroupName": "volumeGroupNameExist", + "modelInfo": { + "modelInvariantId": "7253ff5c-97f0-4b8b-937c-77aeb4d79aa1", + "modelVersionId": "25284168-24bb-4698-8cb4-3f509146eca5", + "modelName": "2017488AdiodVpe..ADIOD_vRE_BV..module-1", + "modelVersion": "6" + } + } + } + }, + "instanceParams": [ + {} + ], + "productFamilyId": "ebc3bc3d-62fd-4a3f-a037-f619df4ff034", + "lcpCloudRegionId": "mtn6", + "tenantId": "19c5ade915eb461e8af52fb2fd8cd1f2", + "lineOfBusiness": "zzz1", + "platformName": "platform", + "instanceName": "2017488_ADIODvPE", + "modelInfo": { + "modelInvariantId": "72e465fe-71b1-4e7b-b5ed-9496118ff7a8", + "modelVersionId": "69e09f68-8b63-4cc9-b9ff-860960b5db09", + "modelName": "2017488_ADIODvPE", + "modelVersion": "5.0", + "modelCustomizationId": "1da7b585-5e61-4993-b95e-8e6606c81e45", + "modelCustomizationName": "2017488_ADIODvPE 0" + }, + "isUserProvidedNaming": true + } + }, + "instanceParams": [ + {} + ], + "globalSubscriberId": "e433710f-9217-458d-a79d-1c7aff376d89", + "productFamilyId": "17cc1042-527b-11e6-beb8-9e71128cae77", + "subscriptionServiceType": "VIRTUAL USP", + "lcpCloudRegionId": "AAIAIC25", + "tenantId": "092eb9e8e4b7412e8787dd091bc58e86", + "aicZoneId": "DKJ1", + "projectName": "DFW", + "owningEntityId": "aaa1", + "instanceName": "Instance-Name", + "bulkSize": 1, + "modelInfo": { + "modelInvariantId": "e49fbd11-e60c-4a8e-b4bf-30fbe8f4fcc0", + "modelVersionId": "1a80c596-27e5-4ca9-b5bb-e03a7fd4c0fd", + "modelName": "action-data", + "modelVersion": "1.0" + }, + "tenantName": "USP-SIP-IC-24335-T-01", + "aicZoneName": "DKJSJDKA-DKJ1", + "isUserProvidedNaming": true + } + } + )); + } + + function generateFormGroup() { + return new FormGroup({ + productFamilyId: new FormControl(), + lcpCloudRegionId: new FormControl(Validators.required), + tenantId: new FormControl({value: null, disabled: false}, Validators.required), + legacyRegion: new FormControl(), + lineOfBusiness: new FormControl(), + platformName: new FormControl(Validators.required), + rollbackOnFailure: new FormControl(Validators.required), + instanceName: new FormControl({value: null}, Validators.compose([Validators.required, NumbersLettersUnderscoreValidator.valid])) + + }); + } + +}); diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.service.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.service.ts new file mode 100644 index 00000000..677895e7 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnf-instance-details.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@angular/core'; +import { isNullOrUndefined } from "util"; +import { FormGroup } from '@angular/forms'; +@Injectable() +export class VnfInstanceDetailsService { + isUnique(serviceInstance : any, serviceId : string, name: string, isEqualToOriginalInstanceName : boolean) : boolean { + const service = serviceInstance[serviceId]; + let countInstanceName = 0; + let countVolumeGroupName = 0; + if(service){ + if(service.instanceName === name) return false; + if(service.vnfs){ + for(let key in service.vnfs){ + if(service.vnfs[key].instanceName === name) { + countInstanceName++; + if((isEqualToOriginalInstanceName && countInstanceName > 1) || (!isEqualToOriginalInstanceName)) return false; + } + if(service.vnfs[key].vfModules){ + for(let vfModule in service.vnfs[key].vfModules){ + if(service.vnfs[key].vfModules[vfModule]) { + for(let module in service.vnfs[key].vfModules[vfModule]){ + if(service.vnfs[key].vfModules[vfModule][module].instanceName === name ) { + countInstanceName++; + if((isEqualToOriginalInstanceName && countInstanceName > 1) || (!isEqualToOriginalInstanceName)) return false; + } + + if(service.vnfs[key].vfModules[vfModule][module].volumeGroupName === name ) { + countVolumeGroupName++; + if((isEqualToOriginalInstanceName && countVolumeGroupName > 1) || (!isEqualToOriginalInstanceName)) return false; + } + } + } + } + } + + } + } + } + return true; + } + + hasApiError(controlName: string, data: Array<any>, form: FormGroup) { + if (!isNullOrUndefined(data)) { + if (!form.controls[controlName].disabled && data.length === 0) { + return true; + } + } + return false; + } + + hasInstanceNameError(form : FormGroup) : boolean { + if(!isNullOrUndefined(form) && !isNullOrUndefined(form.controls['instanceName'])){ + if (form.controls['instanceName'].touched && form.controls['instanceName'].errors && form.controls['instanceName'].errors.pattern) { + return true; + } + } + return false; + } + + hasUniqueError(isNotUniqueInstanceName) : boolean { + return isNotUniqueInstanceName; + } + +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnfPopupDataModel.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnfPopupDataModel.ts new file mode 100644 index 00000000..9a2694c7 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-instance-details/vnfPopupDataModel.ts @@ -0,0 +1,26 @@ +import {Project} from "../../../shared/models/project"; +import {LcpRegion} from "../../../shared/models/lcpRegion"; +import {Tenant} from "../../../shared/models/tenant"; +import {ProductFamily} from "../../../shared/models/productFamily"; +import {SelectOption, SelectOptionInterface} from "../../../shared/models/selectOption"; + +export class VNFPopupDataModel { + productFamilies: ProductFamily[]; + lcpRegions: LcpRegion[]; + lcpRegionsTenantsMap: any; + tenants: Tenant[]; + projects: Project[]; + lineOfBusinesses: SelectOption[]; + platforms: SelectOptionInterface[]; + globalCustomerId: string; + + + constructor(){ + this.lcpRegions = []; + this.lcpRegionsTenantsMap = {}; + this.tenants = []; + this.productFamilies = []; + this.lineOfBusinesses = []; + this.platforms = []; + } +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-popup-service.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup-service.ts new file mode 100644 index 00000000..7dbe9b11 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup-service.ts @@ -0,0 +1,55 @@ +import {Injectable} from "@angular/core"; +import {ServiceNodeTypeToModelKeyMapper} from "../../shared/models/serviceNodeTypeToModelKeyMapper"; +import {ServiceNodeTypes} from "../../shared/models/ServiceNodeTypes"; +import {VfModule} from "../../shared/models/vfModule"; +import {ServicePlanningService} from "../../services/service-planning.service"; +import {VNFModel} from "../../shared/models/vnfModel"; +import * as _ from 'lodash'; +import {isNullOrUndefined} from "util"; +import {NumbersLettersUnderscoreValidator} from '../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; +import {FormGroup} from '@angular/forms'; +import {VnfInstanceDetailsComponent} from './vnf-instance-details/vnf-instance-details.component'; +import {VnfInstanceDetailsService} from './vnf-instance-details/vnf-instance-details.service'; + +@Injectable() +export class VnfPopupService { + + constructor(private _servicePlanningService : ServicePlanningService, private _vnfInstanceDetailsService : VnfInstanceDetailsService) { + } + + public getModelFromResponse(result : any, modelType : string, modelName:string) { + let model = null; + let rawModel = _.get(result, [ServiceNodeTypeToModelKeyMapper[modelType], modelName]); + if (!rawModel) return; + + if (modelType === ServiceNodeTypes.VFmodule) { + model = new VfModule(rawModel); + } + else { + model = new VNFModel(rawModel); + } + return model; + } + + onControlError(servicePopupDataModel : VnfInstanceDetailsComponent, serviceInstanceDetailsFormGroup : FormGroup, isNotUniqueInstanceName : boolean, isNotUniqueVolumeGroupName : boolean) : boolean{ + if(this._vnfInstanceDetailsService.hasUniqueError(isNotUniqueInstanceName) || isNotUniqueVolumeGroupName){ + return true; + } + if(!isNullOrUndefined(serviceInstanceDetailsFormGroup.controls['instanceName']) && NumbersLettersUnderscoreValidator.valid(serviceInstanceDetailsFormGroup.controls['instanceName'].value) && serviceInstanceDetailsFormGroup.controls['instanceName'].value != null && serviceInstanceDetailsFormGroup.controls['instanceName'].value.length > 0){ + return true; + } + + const controlName : Array<string> = ['lcpCloudRegionId', 'tenantId', 'lineOfBusiness', 'platformName']; + const selectDataName : Array<string> = ['lcpRegions', 'tenants', 'lineOfBusinesses', 'platforms', 'projects']; + + for(let i = 0 ; i < controlName.length ; i++){ + if (!isNullOrUndefined(servicePopupDataModel.vnfPopupDataModel) && !isNullOrUndefined(servicePopupDataModel.vnfPopupDataModel[selectDataName[i]])) { + if (!isNullOrUndefined(serviceInstanceDetailsFormGroup.controls[controlName[i]]) && !serviceInstanceDetailsFormGroup.controls[controlName[i]].disabled && servicePopupDataModel.vnfPopupDataModel[selectDataName[i]].length === 0) { + return true; + } + } + } + return false; + } + +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.components.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.components.ts new file mode 100644 index 00000000..26e667d4 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.components.ts @@ -0,0 +1,190 @@ +import {Component, OnInit, ViewChild} from "@angular/core"; +import {AaiService} from "../../services/aaiService/aai.service"; +import {ModelInformationItem} from "../../shared/components/model-information/model-information.component"; +import {ActivatedRoute} from "@angular/router"; +import {DialogComponent, DialogService} from "ng2-bootstrap-modal"; +import {InstancePopup} from "../instance-popup/instance-popup.components"; +import {ServiceModel} from "../../shared/models/serviceModel"; +import {Constants} from "../../shared/utils/constants"; +import * as _ from "lodash"; +import {VnfInstance} from "../../shared/models/vnfInstance"; +import {ServiceInstance} from "../../shared/models/serviceInstance"; +import {VnfInstanceDetailsComponent} from "./vnf-instance-details/vnf-instance-details.component"; +import {Subscriber} from "../../shared/models/subscriber"; +import {ServiceNodeTypes} from "../../shared/models/ServiceNodeTypes"; +import {AppState} from "../../store/reducers"; +import {NgRedux} from "@angular-redux/store"; +import {VfModuleInstance} from "../../shared/models/vfModuleInstance"; +import {VnfPopupService} from './vnf-popup-service'; +import {IframeService} from "../../shared/utils/iframe.service"; + +export interface VnfPopupModel { + serviceModelId: string; + modelName: string; + parentModelName: string; + modelType: string; + dynamicInputs: any; + userProvidedNaming: boolean; + isNewVfModule : boolean; +} + +@Component({ + selector: 'vnf-popup', + templateUrl: 'vnf-popup.html', + styleUrls: ['vnf-popup.scss'], + providers: [AaiService, VnfPopupService] +}) + +export class VnfPopupComponent extends DialogComponent<VnfPopupModel, boolean> implements VnfPopupModel, InstancePopup, OnInit { + + @ViewChild(VnfInstanceDetailsComponent) vnfInstanceDetails: VnfInstanceDetailsComponent; + + serviceModelId: string; + modelName: string; + parentModelName: string; + modelType: string; + isNewVfModule : boolean; + model: any; + serviceModel: ServiceModel; + popupTypeName: string; + serviceInstance: ServiceInstance; + vnfInstance: VnfInstance; + dynamicInputs; + userProvidedNaming: boolean; + typeMapperForTitle = { + VF: "VNF", + VFmodule: "Module (Heat stack)" + }; + + modelInformationItems: Array<ModelInformationItem> = []; + isNotUniqueInstanceName : boolean = false; + isNotUniqueVolumeGroupName : boolean = false; + hasGeneralApiError : boolean = false; + + parentElementClassName = 'content'; + + constructor(dialogService: DialogService, protected route: ActivatedRoute, protected _aaiService: AaiService, + private store: NgRedux<AppState>, + private _iframeService : IframeService, + private _vnfPopupService: VnfPopupService) { + super(dialogService); + this.vnfInstance = new VnfInstance(); + } + + updateGeneralErrorSection() : void { + this.hasGeneralApiError = this._vnfPopupService.onControlError( + this.vnfInstanceDetails, + this.vnfInstanceDetails.instanceFormGroup, + this.vnfInstanceDetails.isNotUniqueInstanceName, + this.vnfInstanceDetails.isNotUniqueVolumeGroupName); + } + + ngOnInit(): void { + this.updateServiceModelById(); + this.popupTypeName = this.getModelTypeForPopupTitle(); + this.updateServiceModelById(); + this.updateInstanceFromStore(); + } + + onCancelClick() { + this._iframeService.removeClassCloseModal(this.parentElementClassName); + super.close(); + } + + onServiceInstanceNameChanged(isNotUniqueInstanceName: boolean) : void { + this.isNotUniqueInstanceName = isNotUniqueInstanceName; + } + + onVolumeGroupNameChanged(isNotUniqueVolumeGroupName: boolean) : void { + this.isNotUniqueVolumeGroupName = isNotUniqueVolumeGroupName; + } + + onSetClick() { + this._iframeService.removeClassCloseModal(this.parentElementClassName); + this.result = true; + super.close(); + } + + updateServiceModelById() { + this._aaiService.getServiceModelById(this.serviceModelId).subscribe( + result => { + this.serviceModel = new ServiceModel(result); + this.model = this._vnfPopupService.getModelFromResponse(result, this.modelType, this.modelName); + this.modelInformationItems = this.createModelInformationItems(); + }, + error => { + console.log('error is ', error) + } + ); + } + + updateInstanceFromStore() { + let instance; + const serviceInstance = this.store.getState().service.serviceInstance[this.serviceModelId]; + if (this.modelType === ServiceNodeTypes.VF) { + instance = serviceInstance.vnfs[this.modelName] || new VnfInstance(); + } else { + instance = new VfModuleInstance(); + } + + if (instance.instanceParams && instance.instanceParams[0]) { + this.dynamicInputs = this.dynamicInputs.map(x => { + x.value = (instance.instanceParams[0][x.id]) ? instance.instanceParams[0][x.id] : x.value; + return x; + }); + } + this.vnfInstance = instance; + } + + getModelName(): string { + return this.modelName; + } + + getModelTypeForPopupTitle(): string { + if (_.has(this.typeMapperForTitle, this.modelType)) { + return this.typeMapperForTitle[this.modelType]; + } + return this.modelType; + } + + extractSubscriberNameBySubscriberId(subsriberId: string) { + var result: string = null; + var filteredArray: any = _.filter(this.store.getState().service.subscribers, function (o: Subscriber) { + return o.id === subsriberId + }) + if (filteredArray.length > 0) { + result = filteredArray[0].name; + } + return result; + } + + createModelInformationItems(): Array<ModelInformationItem> { + var serviceInstance = this.store.getState().service.serviceInstance[this.serviceModelId]; + + let items = [ + new ModelInformationItem("Subscriber Name", "subscriberName", [this.extractSubscriberNameBySubscriberId(serviceInstance.globalSubscriberId)], "", true), + new ModelInformationItem("Service Name", "serviceModelName", [this.serviceModel.name], "", true), + + new ModelInformationItem("Service Instance Name", "serviceName", [serviceInstance.instanceName], "", false), + new ModelInformationItem("Model Name", "modelName", [this.model.name], "", true), + new ModelInformationItem("Model version", "modelVersion", [this.model.version], "", true), + new ModelInformationItem("Description", "description", [this.model.description]), + new ModelInformationItem("Category", "category", [this.model.category]), + new ModelInformationItem("Sub Category", "subCategory",[this.model.subCategory]), + new ModelInformationItem("UUID", "uuid", [this.model.uuid], Constants.ServicePopup.TOOLTIP_UUID, true), + new ModelInformationItem("Invariant UUID", "invariantUuid", [this.model.invariantUuid], Constants.ServicePopup.TOOLTIP_INVARIANT_UUID, true), + new ModelInformationItem("Service type", "serviceType", [this.serviceModel.serviceType]), + new ModelInformationItem("Service role", "serviceRole", [this.serviceModel.serviceRole]), + + + ]; + if (this.modelType === 'VFmodule') { + items.push(new ModelInformationItem("Minimum to instantiate", "min", [this.model.min], "", true), + new ModelInformationItem("Maximum to instantiate", "max", this.model.max == undefined ? [1] : [this.model.max], "", true), + new ModelInformationItem("Recommended to instantiate", "initial", [this.model.initial])); + + } + + return items; + } +} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.html b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.html new file mode 100644 index 00000000..d2e043b1 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.html @@ -0,0 +1,54 @@ +<div id="instance-popup" class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" (click)="onCancelClick()" >×</button> + <span [attr.data-tests-id]="'create-modal-title'" class="modal-title">Set a new {{popupTypeName}}</span> + </div> + <div class="modal-body popup-content"> + + <div class="header-left"> + <div>MODEL: <span>"{{popupTypeName}}"</span></div> + </div> + + <div class="header-right"> + {{getModelTypeForPopupTitle()}} Instance Details + </div> + + <div class="model-information"> + <model-information [modelInformationItems]="modelInformationItems"></model-information> + </div> + + <div class="instance-form"> + <vnf-instance-details [dynamicInputs]="dynamicInputs" + [vnfModel]="model" + [modelType]="modelType" + [modelName]="modelName" + [parentModelName]="parentModelName" + [isNewVfModule]="isNewVfModule" + [serviceInstance]="vnfInstance" + [vnfInstance]="vnfInstance" + [serviceUuid]="serviceModelId" + [userProvidedNaming]="userProvidedNaming" + (onSubmitClick)="onSetClick($event)" + (onDataChanged)="updateGeneralErrorSection()" + (onServiceInstanceNameChanged)="onServiceInstanceNameChanged($event)" + (onVolumeGroupNameChanged)="onVolumeGroupNameChanged($event)" + + ></vnf-instance-details> + </div> + + </div> + <div class="modal-footer row" style="padding: 0"> + <div class="col-md-6"> + <div *ngIf="hasGeneralApiError == true"> + <form-general-error [message]="'Page contains errors. Please see details next to the relevant fields.'"></form-general-error> + </div> + </div> + <div class="col-md-6" style="padding: 15px;padding-right: 35px;"> + <button [attr.data-tests-id]="'cancelButton'" type="button" class="btn btn-default cancel" (click)="onCancelClick()"><span>Cancel</span></button> + <input type="submit" value="Set" form="vnfForm" data-tests-id="vnf-form-set" + class="btn btn-success submit" [disabled]="!vnfInstanceDetails?.vnfForm?.valid || isNotUniqueInstanceName || isNotUniqueVolumeGroupName"> + </div> + </div> + </div> +</div> diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.scss b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.scss new file mode 100644 index 00000000..6e606dbb --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.scss @@ -0,0 +1,185 @@ +$grid-border: 1px #d2d2d2 solid; + +#instance-popup { + color: #191919; + + .left-panel { + background: #f2f2f2; + border-right: $grid-border; + } + + .header-common { + height: 100%; + align-items: center; + display: flex; + font-family: OpenSans-Semibold; + font-size: 12px; + } + + .header-text { + padding-left: 30px; + @extend .header-common; + } + + .header-left { + grid-area: header-left; + @extend .header-text; + @extend .left-panel; + border-bottom: $grid-border; + + span { + font-family: OpenSans-Regular; + font-size: 14px; + }; + } + + .header-right { + grid-area: header-right; + + @extend .header-text; + border-bottom: $grid-border; + } + + .quantity-label { + grid-area: quantity-label; + @extend .header-common; + border-bottom: $grid-border; + height: 100%; + font-family: OpenSans-Regular; + } + + .quantity { + grid-area: quantity; + border-left: $grid-border; + border-bottom: $grid-border; + border-top-style: none; + font-family: OpenSans-Semibold; + text-align: start; + text-indent: 10px; + } + + .quantity-select { + width: 78px; + height: 100%; + border: 0; + background: white; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: url('../../../assets/img/chevron.svg') 0 0 no-repeat; + background-size: 24px; + background-position-x: right; + background-position-y: center; + } + input[type="number"]:hover::-webkit-inner-spin-button { + height: 20px; + } + + .model-information { + grid-area: model-information; + + padding: 30px; + overflow: auto; + @extend .left-panel; + } + + .instance-form { + grid-area: instance-form; + } + + .popup-content { + display: grid; + grid-template-columns: 400px auto 30px 93px; + grid-template-rows: 50px calc(100vh - 180px); + grid-template-areas: + "header-left header-right header-right header-right" + "model-information instance-form instance-form instance-form"; + padding: 0; + } +} + +.modal { + background-color: #191919; + opacity: 0.8; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 0; +} +@media (min-width: 1150px) { + .popup-content { + grid-template-rows: 30px 680px; + } +} + +.modal-content { + border-radius: 0; + box-shadow: none; + border: none; +} + +.modal-footer { + .cancel { + width: 120px; + height: 36px; + background: #ffffff; + border: 1px solid #009fdb; + border-radius: 2px; + span { + font-family: OpenSans-Regular; + font-size: 14px; + color: #009fdb; + line-height: 16px; + } + } + + .submit { + width: 120px; + height: 36px; + background: #009fdb; + border-radius: 2px; + border-color: #009fdb; + span { + font-family: OpenSans-Regular; + font-size: 14px; + color: #FFFFFF; + line-height: 16px; + } + } +} + +.modal-header { + background-color: #009fdb; + + padding-bottom: 13px; + padding-top: 13px; + padding-left: 29px; + padding-right: 21px; + + .close { + font-size: 32px; + font-weight: 200; + color: #d8d8d8; + text-shadow: none; + filter: none; + opacity: 1; + } + + .modal-title { + font-family: OpenSans-Regular; + font-size: 24px; + color: #fff; + line-height: 34px; + } +} +// +//@media (min-width: 1200px) { +// .service-model, +// .service-instance { +// width: 1050px; +// margin: 30px auto; +// } +//} diff --git a/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.service.spec.ts b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.service.spec.ts new file mode 100644 index 00000000..02296f72 --- /dev/null +++ b/vid-webpack-master/src/app/components/vnf-popup/vnf-popup.service.spec.ts @@ -0,0 +1,827 @@ +import {VnfPopupService} from './vnf-popup-service'; +import {ServicePlanningService} from '../../services/service-planning.service'; +import {ServiceNodeTypes} from '../../shared/models/ServiceNodeTypes'; +import {NgRedux} from '@angular-redux/store'; +import {VNFModel} from '../../shared/models/vnfModel'; +import {VfModule} from '../../shared/models/vfModule'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; +import {NumbersLettersUnderscoreValidator} from '../../shared/components/validators/numbersLettersUnderscore/numbersLettersUnderscore.validator'; +import {VnfInstanceDetailsService} from './vnf-instance-details/vnf-instance-details.service'; +import {ReflectiveInjector} from '@angular/core'; + +export class MockAppStore<T> { +} + +describe('Vnf popup service', () => { + let injector; + let service: VnfPopupService; + let fg: FormGroup; + let data = generateModelData(); + let servicePopupDataModel = generateServicePopupDataModel(); + let form: FormGroup = generateFormGroup(); + beforeEach(() => { + + let injector = ReflectiveInjector.resolveAndCreate([ + VnfPopupService, + ServicePlanningService, + VnfInstanceDetailsService, + {provide: FormGroup, useClass: MockAppStore}, + {provide: NgRedux, useClass: MockAppStore} + ]); + + service = injector.get(VnfPopupService); + fg = injector.get(FormGroup); + }); + + describe('#updateVnfDataFromModel', () => { + it('update vnf data from model should return new vnf', (done: DoneFn) => { + let vnf: VNFModel = service.getModelFromResponse(data, ServiceNodeTypes.VF, '2017-388_ADIOD-vPE 1'); + + expect(vnf).toEqual(jasmine.any(VNFModel)); + done(); + }); + + it('update wrong vnf data from model should be undefined', (done: DoneFn) => { + let vnf: VNFModel = service.getModelFromResponse(data, ServiceNodeTypes.VF, '2017-388_ADIOD-vPE 3'); + + expect(vnf).toBeUndefined(); + done(); + }); + + it('update vfModule data from model should return new vfModule', (done: DoneFn) => { + let vfModule: VfModule = service.getModelFromResponse(data, ServiceNodeTypes.VFmodule, '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vRE_BV..module-1'); + + expect(vfModule).toEqual(jasmine.any(VfModule)); + done(); + }); + + }); + + + describe('#onControlError', () => { + it('onControlError should return true if instanceName is not legal', (done: DoneFn) => { + form.controls['instanceName'].setValue('aaaa - aaa'); + + let result: boolean = service.onControlError(<any>servicePopupDataModel, form, false, false); + expect(result).toBeTruthy(); + done(); + }); + + it('onControlError should return false if instanceName is legal', (done: DoneFn) => { + + form.controls['instanceName'].setValue('aaaa'); + let result = service.onControlError(<any>servicePopupDataModel, form, false, false); + expect(result).toBeFalsy(); + done(); + }); + + it('onControlError should return true if instanceName is not unique', (done: DoneFn) => { + + form.controls['instanceName'].setValue('aaaa'); + let result = service.onControlError(<any>servicePopupDataModel, form, true, false); + expect(result).toBeTruthy(); + done(); + }); + + it('onControlError should return true if lcpRegions is empty', (done: DoneFn) => { + servicePopupDataModel.vnfPopupDataModel['lcpRegions'] = []; + let result = service.onControlError(<any>servicePopupDataModel, form, true, false); + expect(result).toBeTruthy(); + done(); + }); + + it('onControlError should return true if isNotUniqueVolumeGroupName is true', (done: DoneFn) => { + let result = service.onControlError(<any>servicePopupDataModel, form, true, true); + expect(result).toBeTruthy(); + done(); + }) + }); + + + function generateServicePopupDataModel() { + return { + 'vnfPopupDataModel': JSON.parse('{"tenants" : [1,2,3],"lcpRegions":[1,2,3],"lcpRegionsTenantsMap":{},"productFamilies":[1,2,3],"lineOfBusinesses":[{"id":"ECOMP","name":"ECOMP"},{"id":"zzz1","name":"zzz1"}],"platforms":[{"id":"platform","name":"platform"},{"id":"xxx1","name":"xxx1"}],"rollbackOnFailure":[{"id":"true","name":"Rollback"}]}') + } + } + + function generateFormGroup() { + return new FormGroup({ + productFamilyId: new FormControl(), + lcpCloudRegionId: new FormControl(Validators.required), + tenantId: new FormControl({value: null, disabled: false}, Validators.required), + legacyRegion: new FormControl(), + lineOfBusiness: new FormControl(), + platformName: new FormControl(Validators.required), + instanceName: new FormControl({value: null}, Validators.compose([Validators.required, NumbersLettersUnderscoreValidator.valid])) + }); + } + + function generateModelData() { + return JSON.parse(JSON.stringify( + { + 'service': { + 'uuid': '2f80c596-27e5-4ca9-b5bb-e03a7fd4c0fd', + 'invariantUuid': 'e49fbd11-e60c-4a8e-b4bf-30fbe8f4fcc0', + 'name': 'action-data', + 'version': '1.0', + 'toscaModelURL': null, + 'category': '', + 'serviceType': '', + 'serviceRole': '', + 'description': '', + 'serviceEcompNaming': 'true', + 'instantiationType': 'ClientConfig', + 'inputs': { + '2017488_adiodvpe0_ASN': { + 'type': 'string', + 'description': 'AV/PE', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'AV_vPE' + }, + 'adiodvpe0_bandwidth': { + 'type': 'string', + 'description': 'Requested VPE bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '10' + }, + '2017488_adiodvpe0_vnf_instance_name': { + 'type': 'string', + 'description': 'The hostname assigned to the vpe.', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'mtnj309me6' + }, + '2017488_adiodvpe0_vnf_config_template_version': { + 'type': 'string', + 'description': 'VPE Software Version', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '17.2' + }, + '2017488_adiodvpe0_AIC_CLLI': { + 'type': 'string', + 'description': 'AIC Site CLLI', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'ATLMY8GA' + }, + 'adiodvpe0_bandwidth_units': { + 'type': 'string', + 'description': 'Units of bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'Gbps' + } + } + }, + 'vnfs': { + '2017-388_ADIOD-vPE 1': { + 'uuid': '0903e1c0-8e03-4936-b5c2-260653b96413', + 'invariantUuid': '00beb8f9-6d39-452f-816d-c709b9cbb87d', + 'description': 'Name ADIOD vPE Description The provider edge function for the ADIOD service supported by the Junipers VMX product Category Router Vendor Juniper Vendor Release Code 17.2 Owners Mary Fragale. Updated 9-25 to use v8.0 of the Juniper Valid 2 VLM', + 'name': '2017-388_ADIOD-vPE', + 'version': '1.0', + 'customizationUuid': '280dec31-f16d-488b-9668-4aae55d6648a', + 'inputs': { + 'vnf_config_template_version': { + 'type': 'string', + 'description': 'VPE Software Version', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '17.2' + }, + 'bandwidth_units': { + 'type': 'string', + 'description': 'Units of bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'Gbps' + }, + 'bandwidth': { + 'type': 'string', + 'description': 'Requested VPE bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '10' + }, + 'AIC_CLLI': { + 'type': 'string', + 'description': 'AIC Site CLLI', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'ATLMY8GA' + }, + 'ASN': { + 'type': 'string', + 'description': 'AV/PE', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'AV_vPE' + }, + 'vnf_instance_name': { + 'type': 'string', + 'description': 'The hostname assigned to the vpe.', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'mtnj309me6' + } + }, + 'commands': { + 'vnf_config_template_version': { + 'displayName': 'vnf_config_template_version', + 'command': 'get_input', + 'inputName': '2017488_adiodvpe0_vnf_config_template_version' + }, + 'bandwidth_units': { + 'displayName': 'bandwidth_units', + 'command': 'get_input', + 'inputName': 'adiodvpe0_bandwidth_units' + }, + 'bandwidth': {'displayName': 'bandwidth', 'command': 'get_input', 'inputName': 'adiodvpe0_bandwidth'}, + 'AIC_CLLI': {'displayName': 'AIC_CLLI', 'command': 'get_input', 'inputName': '2017488_adiodvpe0_AIC_CLLI'}, + 'ASN': {'displayName': 'ASN', 'command': 'get_input', 'inputName': '2017488_adiodvpe0_ASN'}, + 'vnf_instance_name': { + 'displayName': 'vnf_instance_name', + 'command': 'get_input', + 'inputName': '2017488_adiodvpe0_vnf_instance_name' + } + }, + 'properties': { + 'vmxvre_retype': 'RE-VMX', + 'vnf_config_template_version': 'get_input:2017488_adiodvpe0_vnf_config_template_version', + 'sriov44_net_id': '48d399b3-11ee-48a8-94d2-f0ea94d6be8d', + 'int_ctl_net_id': '2f323477-6936-4d01-ac53-d849430281d9', + 'vmxvpfe_sriov41_0_port_mac': '00:11:22:EF:AC:DF', + 'int_ctl_net_name': 'VMX-INTXI', + 'vmx_int_ctl_prefix': '128.0.0.0', + 'sriov43_net_id': 'da349ca1-6de9-4548-be88-2d88e99bfef5', + 'sriov42_net_id': '760669ba-013d-4d9b-b0e7-4151fe2e6279', + 'sriov41_net_id': '25ad52d5-c165-40f8-b3b0-ddfc2373280a', + 'nf_type': 'vPE', + 'vmxvpfe_int_ctl_ip_1': '128.0.0.16', + 'is_AVPN_service': 'false', + 'vmx_RSG_name': 'vREXI-affinity', + 'vmx_int_ctl_forwarding': 'l2', + 'vmxvre_oam_ip_0': '10.40.123.5', + 'vmxvpfe_sriov44_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvpfe_sriov41_0_port_vlanstrip': 'false', + 'vmxvpfe_sriov42_0_port_vlanfilter': '4001', + 'vmxvpfe_sriov44_0_port_unknownunicastallow': 'true', + 'vmxvre_image_name_0': 'VRE-ENGINE_17.2-S2.1.qcow2', + 'vmxvre_instance': '0', + 'vmxvpfe_sriov43_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvre_flavor_name': 'ns.c1r16d32.v5', + 'vmxvpfe_volume_size_0': '40.0', + 'vmxvpfe_sriov43_0_port_vlanfilter': '4001', + 'nf_naming': '{ecomp_generated_naming=true}', + 'nf_naming_code': 'Navneet', + 'vmxvre_name_0': 'vREXI', + 'vmxvpfe_sriov42_0_port_vlanstrip': 'false', + 'vmxvpfe_volume_name_0': 'vPFEXI_FBVolume', + 'vmx_RSG_id': 'bd89a33c-13c3-4a04-8fde-1a57eb123141', + 'vmxvpfe_image_name_0': 'VPE_ROUTING-ENGINE_17.2R1-S2.1.qcow2', + 'vmxvpfe_sriov43_0_port_unknownunicastallow': 'true', + 'vmxvpfe_sriov44_0_port_unknownmulticastallow': 'true', + 'vmxvre_console': 'vidconsole', + 'vmxvpfe_sriov44_0_port_vlanfilter': '4001', + 'vmxvpfe_sriov42_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvpfe_volume_id_0': '47cede15-da2f-4397-a101-aa683220aff3', + 'vmxvpfe_sriov42_0_port_unknownmulticastallow': 'true', + 'vmxvpfe_sriov44_0_port_vlanstrip': 'false', + 'vf_module_id': '123', + 'nf_function': 'JAI', + 'vmxvpfe_sriov43_0_port_unknownmulticastallow': 'true', + 'vmxvre_int_ctl_ip_0': '128.0.0.1', + 'AIC_CLLI': 'get_input:2017488_adiodvpe0_AIC_CLLI', + 'vnf_name': 'mtnj309me6vre', + 'vmxvpfe_sriov41_0_port_unknownunicastallow': 'true', + 'vmxvre_volume_type_1': 'HITACHI', + 'vmxvpfe_sriov44_0_port_broadcastallow': 'true', + 'vmxvre_volume_type_0': 'HITACHI', + 'vmxvpfe_volume_type_0': 'HITACHI', + 'vmxvpfe_sriov43_0_port_broadcastallow': 'true', + 'bandwidth_units': 'get_input:adiodvpe0_bandwidth_units', + 'vnf_id': '123', + 'vmxvre_oam_prefix': '24', + 'availability_zone_0': 'mtpocfo-kvm-az01', + 'ASN': 'get_input:2017488_adiodvpe0_ASN', + 'vmxvre_chassis_i2cid': '161', + 'vmxvpfe_name_0': 'vPFEXI', + 'bandwidth': 'get_input:adiodvpe0_bandwidth', + 'availability_zone_max_count': '1', + 'vmxvre_volume_size_0': '45.0', + 'vmxvre_volume_size_1': '50.0', + 'vmxvpfe_sriov42_0_port_broadcastallow': 'true', + 'vmxvre_oam_gateway': '10.40.123.1', + 'vmxvre_volume_name_1': 'vREXI_FAVolume', + 'vmxvre_ore_present': '0', + 'vmxvre_volume_name_0': 'vREXI_FBVolume', + 'vmxvre_type': '0', + 'vnf_instance_name': 'get_input:2017488_adiodvpe0_vnf_instance_name', + 'vmxvpfe_sriov41_0_port_unknownmulticastallow': 'true', + 'oam_net_id': 'b95eeb1d-d55d-4827-abb4-8ebb94941429', + 'vmx_int_ctl_len': '24', + 'vmxvpfe_sriov43_0_port_vlanstrip': 'false', + 'vmxvpfe_sriov41_0_port_broadcastallow': 'true', + 'vmxvre_volume_id_1': '6e86797e-03cd-4fdc-ba72-2957119c746d', + 'vmxvpfe_sriov41_0_port_vlanfilter': '4001', + 'nf_role': 'Testing', + 'vmxvre_volume_id_0': 'f4eacb79-f687-4e9d-b760-21847c8bb15a', + 'vmxvpfe_sriov42_0_port_unknownunicastallow': 'true', + 'vmxvpfe_flavor_name': 'ns.c20r16d25.v5' + }, + 'type': 'VF', + 'modelCustomizationName': '2017-388_ADIOD-vPE 1', + 'vfModules': {}, + 'volumeGroups': {} + }, + '2017-388_ADIOD-vPE 0': { + 'uuid': 'afacccf6-397d-45d6-b5ae-94c39734b168', + 'invariantUuid': '72e465fe-71b1-4e7b-b5ed-9496118ff7a8', + 'description': 'Name ADIOD vPE Description The provider edge function for the ADIOD service supported by the Junipers VMX product Category Router Vendor Juniper Vendor Release Code 17.2 Owners Mary Fragale. Updated 9-25 to use v8.0 of the Juniper Valid 2 VLM', + 'name': '2017-388_ADIOD-vPE', + 'version': '4.0', + 'customizationUuid': 'b3c76f73-eeb5-4fb6-9d31-72a889f1811c', + 'inputs': { + 'vnf_config_template_version': { + 'type': 'string', + 'description': 'VPE Software Version', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '17.2' + }, + 'bandwidth_units': { + 'type': 'string', + 'description': 'Units of bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'Gbps' + }, + 'bandwidth': { + 'type': 'string', + 'description': 'Requested VPE bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '10' + }, + 'AIC_CLLI': { + 'type': 'string', + 'description': 'AIC Site CLLI', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'ATLMY8GA' + }, + 'ASN': { + 'type': 'string', + 'description': 'AV/PE', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'AV_vPE' + }, + 'vnf_instance_name': { + 'type': 'string', + 'description': 'The hostname assigned to the vpe.', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'mtnj309me6' + } + }, + 'commands': { + 'vnf_config_template_version': { + 'displayName': 'vnf_config_template_version', + 'command': 'get_input', + 'inputName': '2017488_adiodvpe0_vnf_config_template_version' + }, + 'bandwidth_units': { + 'displayName': 'bandwidth_units', + 'command': 'get_input', + 'inputName': 'adiodvpe0_bandwidth_units' + }, + 'bandwidth': {'displayName': 'bandwidth', 'command': 'get_input', 'inputName': 'adiodvpe0_bandwidth'}, + 'AIC_CLLI': {'displayName': 'AIC_CLLI', 'command': 'get_input', 'inputName': '2017488_adiodvpe0_AIC_CLLI'}, + 'ASN': {'displayName': 'ASN', 'command': 'get_input', 'inputName': '2017488_adiodvpe0_ASN'}, + 'vnf_instance_name': { + 'displayName': 'vnf_instance_name', + 'command': 'get_input', + 'inputName': '2017488_adiodvpe0_vnf_instance_name' + } + }, + 'properties': { + 'vmxvre_retype': 'RE-VMX', + 'vnf_config_template_version': 'get_input:2017488_adiodvpe0_vnf_config_template_version', + 'sriov44_net_id': '48d399b3-11ee-48a8-94d2-f0ea94d6be8d', + 'int_ctl_net_id': '2f323477-6936-4d01-ac53-d849430281d9', + 'vmxvpfe_sriov41_0_port_mac': '00:11:22:EF:AC:DF', + 'int_ctl_net_name': 'VMX-INTXI', + 'vmx_int_ctl_prefix': '128.0.0.0', + 'sriov43_net_id': 'da349ca1-6de9-4548-be88-2d88e99bfef5', + 'sriov42_net_id': '760669ba-013d-4d9b-b0e7-4151fe2e6279', + 'sriov41_net_id': '25ad52d5-c165-40f8-b3b0-ddfc2373280a', + 'nf_type': 'vPE', + 'vmxvpfe_int_ctl_ip_1': '128.0.0.16', + 'is_AVPN_service': 'false', + 'vmx_RSG_name': 'vREXI-affinity', + 'vmx_int_ctl_forwarding': 'l2', + 'vmxvre_oam_ip_0': '10.40.123.5', + 'vmxvpfe_sriov44_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvpfe_sriov41_0_port_vlanstrip': 'false', + 'vmxvpfe_sriov42_0_port_vlanfilter': '4001', + 'vmxvpfe_sriov44_0_port_unknownunicastallow': 'true', + 'vmxvre_image_name_0': 'VRE-ENGINE_17.2-S2.1.qcow2', + 'vmxvre_instance': '0', + 'vmxvpfe_sriov43_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvre_flavor_name': 'ns.c1r16d32.v5', + 'vmxvpfe_volume_size_0': '40.0', + 'vmxvpfe_sriov43_0_port_vlanfilter': '4001', + 'nf_naming': '{ecomp_generated_naming=true}', + 'nf_naming_code': 'Navneet', + 'vmxvre_name_0': 'vREXI', + 'vmxvpfe_sriov42_0_port_vlanstrip': 'false', + 'vmxvpfe_volume_name_0': 'vPFEXI_FBVolume', + 'vmx_RSG_id': 'bd89a33c-13c3-4a04-8fde-1a57eb123141', + 'vmxvpfe_image_name_0': 'VPE_ROUTING-ENGINE_17.2R1-S2.1.qcow2', + 'vmxvpfe_sriov43_0_port_unknownunicastallow': 'true', + 'vmxvpfe_sriov44_0_port_unknownmulticastallow': 'true', + 'vmxvre_console': 'vidconsole', + 'vmxvpfe_sriov44_0_port_vlanfilter': '4001', + 'vmxvpfe_sriov42_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvpfe_volume_id_0': '47cede15-da2f-4397-a101-aa683220aff3', + 'vmxvpfe_sriov42_0_port_unknownmulticastallow': 'true', + 'vmxvpfe_sriov44_0_port_vlanstrip': 'false', + 'vf_module_id': '123', + 'nf_function': 'JAI', + 'vmxvpfe_sriov43_0_port_unknownmulticastallow': 'true', + 'vmxvre_int_ctl_ip_0': '128.0.0.1', + 'AIC_CLLI': 'get_input:2017488_adiodvpe0_AIC_CLLI', + 'vnf_name': 'mtnj309me6vre', + 'vmxvpfe_sriov41_0_port_unknownunicastallow': 'true', + 'vmxvre_volume_type_1': 'HITACHI', + 'vmxvpfe_sriov44_0_port_broadcastallow': 'true', + 'vmxvre_volume_type_0': 'HITACHI', + 'vmxvpfe_volume_type_0': 'HITACHI', + 'vmxvpfe_sriov43_0_port_broadcastallow': 'true', + 'bandwidth_units': 'get_input:adiodvpe0_bandwidth_units', + 'vnf_id': '123', + 'vmxvre_oam_prefix': '24', + 'availability_zone_0': 'mtpocfo-kvm-az01', + 'ASN': 'get_input:2017488_adiodvpe0_ASN', + 'vmxvre_chassis_i2cid': '161', + 'vmxvpfe_name_0': 'vPFEXI', + 'bandwidth': 'get_input:adiodvpe0_bandwidth', + 'availability_zone_max_count': '1', + 'vmxvre_volume_size_0': '45.0', + 'vmxvre_volume_size_1': '50.0', + 'vmxvpfe_sriov42_0_port_broadcastallow': 'true', + 'vmxvre_oam_gateway': '10.40.123.1', + 'vmxvre_volume_name_1': 'vREXI_FAVolume', + 'vmxvre_ore_present': '0', + 'vmxvre_volume_name_0': 'vREXI_FBVolume', + 'vmxvre_type': '0', + 'vnf_instance_name': 'get_input:2017488_adiodvpe0_vnf_instance_name', + 'vmxvpfe_sriov41_0_port_unknownmulticastallow': 'true', + 'oam_net_id': 'b95eeb1d-d55d-4827-abb4-8ebb94941429', + 'vmx_int_ctl_len': '24', + 'vmxvpfe_sriov43_0_port_vlanstrip': 'false', + 'vmxvpfe_sriov41_0_port_broadcastallow': 'true', + 'vmxvre_volume_id_1': '6e86797e-03cd-4fdc-ba72-2957119c746d', + 'vmxvpfe_sriov41_0_port_vlanfilter': '4001', + 'nf_role': 'Testing', + 'vmxvre_volume_id_0': 'f4eacb79-f687-4e9d-b760-21847c8bb15a', + 'vmxvpfe_sriov42_0_port_unknownunicastallow': 'true', + 'vmxvpfe_flavor_name': 'ns.c20r16d25.v5' + }, + 'type': 'VF', + 'modelCustomizationName': '2017-388_ADIOD-vPE 0', + 'vfModules': {}, + 'volumeGroups': {} + }, + '2017488_ADIODvPE 0': { + 'uuid': '69e09f68-8b63-4cc9-b9ff-860960b5db09', + 'invariantUuid': '72e465fe-71b1-4e7b-b5ed-9496118ff7a8', + 'description': 'Name ADIOD vPE Description The provider edge function for the ADIOD service supported by the Junipers VMX product Category Router Vendor Juniper Vendor Release Code 17.2 Owners Mary Fragale. Updated 9-25 to use v8.0 of the Juniper Valid 2 VLM', + 'name': '2017488_ADIODvPE', + 'version': '5.0', + 'customizationUuid': '1da7b585-5e61-4993-b95e-8e6606c81e45', + 'inputs': { + 'vnf_config_template_version': { + 'type': 'string', + 'description': 'VPE Software Version', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '17.2' + }, + 'bandwidth_units': { + 'type': 'string', + 'description': 'Units of bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'Gbps' + }, + 'bandwidth': { + 'type': 'string', + 'description': 'Requested VPE bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '10' + }, + 'AIC_CLLI': { + 'type': 'string', + 'description': 'AIC Site CLLI', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'ATLMY8GA' + }, + 'ASN': { + 'type': 'string', + 'description': 'AV/PE', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'AV_vPE' + }, + 'vnf_instance_name': { + 'type': 'string', + 'description': 'The hostname assigned to the vpe.', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'mtnj309me6' + } + }, + 'commands': { + 'vnf_config_template_version': { + 'displayName': 'vnf_config_template_version', + 'command': 'get_input', + 'inputName': '2017488_adiodvpe0_vnf_config_template_version' + }, + 'bandwidth_units': { + 'displayName': 'bandwidth_units', + 'command': 'get_input', + 'inputName': 'adiodvpe0_bandwidth_units' + }, + 'bandwidth': {'displayName': 'bandwidth', 'command': 'get_input', 'inputName': 'adiodvpe0_bandwidth'}, + 'AIC_CLLI': {'displayName': 'AIC_CLLI', 'command': 'get_input', 'inputName': '2017488_adiodvpe0_AIC_CLLI'}, + 'ASN': {'displayName': 'ASN', 'command': 'get_input', 'inputName': '2017488_adiodvpe0_ASN'}, + 'vnf_instance_name': { + 'displayName': 'vnf_instance_name', + 'command': 'get_input', + 'inputName': '2017488_adiodvpe0_vnf_instance_name' + } + }, + 'properties': { + 'vmxvre_retype': 'RE-VMX', + 'vnf_config_template_version': 'get_input:2017488_adiodvpe0_vnf_config_template_version', + 'sriov44_net_id': '48d399b3-11ee-48a8-94d2-f0ea94d6be8d', + 'int_ctl_net_id': '2f323477-6936-4d01-ac53-d849430281d9', + 'vmxvpfe_sriov41_0_port_mac': '00:11:22:EF:AC:DF', + 'int_ctl_net_name': 'VMX-INTXI', + 'vmx_int_ctl_prefix': '128.0.0.0', + 'sriov43_net_id': 'da349ca1-6de9-4548-be88-2d88e99bfef5', + 'sriov42_net_id': '760669ba-013d-4d9b-b0e7-4151fe2e6279', + 'sriov41_net_id': '25ad52d5-c165-40f8-b3b0-ddfc2373280a', + 'nf_type': 'vPE', + 'vmxvpfe_int_ctl_ip_1': '128.0.0.16', + 'is_AVPN_service': 'false', + 'vmx_RSG_name': 'vREXI-affinity', + 'vmx_int_ctl_forwarding': 'l2', + 'vmxvre_oam_ip_0': '10.40.123.5', + 'vmxvpfe_sriov44_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvpfe_sriov41_0_port_vlanstrip': 'false', + 'vmxvpfe_sriov42_0_port_vlanfilter': '4001', + 'vmxvpfe_sriov44_0_port_unknownunicastallow': 'true', + 'vmxvre_image_name_0': 'VRE-ENGINE_17.2-S2.1.qcow2', + 'vmxvre_instance': '0', + 'vmxvpfe_sriov43_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvre_flavor_name': 'ns.c1r16d32.v5', + 'vmxvpfe_volume_size_0': '40.0', + 'vmxvpfe_sriov43_0_port_vlanfilter': '4001', + 'nf_naming': '{ecomp_generated_naming=true}', + 'nf_naming_code': 'Navneet', + 'vmxvre_name_0': 'vREXI', + 'vmxvpfe_sriov42_0_port_vlanstrip': 'false', + 'vmxvpfe_volume_name_0': 'vPFEXI_FBVolume', + 'vmx_RSG_id': 'bd89a33c-13c3-4a04-8fde-1a57eb123141', + 'vmxvpfe_image_name_0': 'VPE_ROUTING-ENGINE_17.2R1-S2.1.qcow2', + 'vmxvpfe_sriov43_0_port_unknownunicastallow': 'true', + 'vmxvpfe_sriov44_0_port_unknownmulticastallow': 'true', + 'vmxvre_console': 'vidconsole', + 'vmxvpfe_sriov44_0_port_vlanfilter': '4001', + 'vmxvpfe_sriov42_0_port_mac': '00:11:22:EF:AC:DF', + 'vmxvpfe_volume_id_0': '47cede15-da2f-4397-a101-aa683220aff3', + 'vmxvpfe_sriov42_0_port_unknownmulticastallow': 'true', + 'vmxvpfe_sriov44_0_port_vlanstrip': 'false', + 'vf_module_id': '123', + 'nf_function': 'JAI', + 'vmxvpfe_sriov43_0_port_unknownmulticastallow': 'true', + 'vmxvre_int_ctl_ip_0': '128.0.0.1', + 'AIC_CLLI': 'get_input:2017488_adiodvpe0_AIC_CLLI', + 'vnf_name': 'mtnj309me6vre', + 'vmxvpfe_sriov41_0_port_unknownunicastallow': 'true', + 'vmxvre_volume_type_1': 'HITACHI', + 'vmxvpfe_sriov44_0_port_broadcastallow': 'true', + 'vmxvre_volume_type_0': 'HITACHI', + 'vmxvpfe_volume_type_0': 'HITACHI', + 'vmxvpfe_sriov43_0_port_broadcastallow': 'true', + 'bandwidth_units': 'get_input:adiodvpe0_bandwidth_units', + 'vnf_id': '123', + 'vmxvre_oam_prefix': '24', + 'availability_zone_0': 'mtpocfo-kvm-az01', + 'ASN': 'get_input:2017488_adiodvpe0_ASN', + 'vmxvre_chassis_i2cid': '161', + 'vmxvpfe_name_0': 'vPFEXI', + 'bandwidth': 'get_input:adiodvpe0_bandwidth', + 'availability_zone_max_count': '1', + 'vmxvre_volume_size_0': '45.0', + 'vmxvre_volume_size_1': '50.0', + 'vmxvpfe_sriov42_0_port_broadcastallow': 'true', + 'vmxvre_oam_gateway': '10.40.123.1', + 'vmxvre_volume_name_1': 'vREXI_FAVolume', + 'vmxvre_ore_present': '0', + 'vmxvre_volume_name_0': 'vREXI_FBVolume', + 'vmxvre_type': '0', + 'vnf_instance_name': 'get_input:2017488_adiodvpe0_vnf_instance_name', + 'vmxvpfe_sriov41_0_port_unknownmulticastallow': 'true', + 'oam_net_id': 'b95eeb1d-d55d-4827-abb4-8ebb94941429', + 'vmx_int_ctl_len': '24', + 'vmxvpfe_sriov43_0_port_vlanstrip': 'false', + 'vmxvpfe_sriov41_0_port_broadcastallow': 'true', + 'vmxvre_volume_id_1': '6e86797e-03cd-4fdc-ba72-2957119c746d', + 'vmxvpfe_sriov41_0_port_vlanfilter': '4001', + 'nf_role': 'Testing', + 'vmxvre_volume_id_0': 'f4eacb79-f687-4e9d-b760-21847c8bb15a', + 'vmxvpfe_sriov42_0_port_unknownunicastallow': 'true', + 'vmxvpfe_flavor_name': 'ns.c20r16d25.v5' + }, + 'type': 'VF', + 'modelCustomizationName': '2017488_ADIODvPE 0', + 'vfModules': { + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vRE_BV..module-1': { + 'uuid': '25284168-24bb-4698-8cb4-3f509146eca5', + 'invariantUuid': '7253ff5c-97f0-4b8b-937c-77aeb4d79aa1', + 'customizationUuid': 'f7e7c365-60cf-49a9-9ebf-a1aa11b9d401', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_vRE_BV..module-1', + 'version': '6', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_vRE_BV..module-1', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': true, + 'inputs': { + '2017488_adiodvpe0_vnf_config_template_version': { + 'type': 'string', + 'description': 'VPE Software Version', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '17.2' + }, + '2017488_adiodvpe0_AIC_CLLI': { + 'type': 'string', + 'description': 'AIC Site CLLI', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'ATLMY8GA' + } + } + }, + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_base_vPE_BV..module-0': { + 'uuid': 'f8360508-3f17-4414-a2ed-6bc71161e8db', + 'invariantUuid': 'b34833bb-6aa9-4ad6-a831-70b06367a091', + 'customizationUuid': 'a55961b2-2065-4ab0-a5b7-2fcee1c227e3', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_base_vPE_BV..module-0', + 'version': '5', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_base_vPE_BV..module-0', + 'properties': {'minCountInstances': 1, 'maxCountInstances': 1, 'initialCount': 1}, + 'commands': {}, + 'volumeGroupAllowed': false, + 'inputs': { + '2017488_adiodvpe0_ASN': { + 'type': 'string', + 'description': 'AV/PE', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': 'AV_vPE' + }, + 'adiodvpe0_bandwidth': { + 'type': 'string', + 'description': 'Requested VPE bandwidth', + 'entry_schema': null, + 'constraints': [], + 'required': true, + 'default': '10' + } + } + }, + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vPFE_BV..module-2': { + 'uuid': '0a0dd9d4-31d3-4c3a-ae89-a02f383e6a9a', + 'invariantUuid': 'eff8cc59-53a1-4101-aed7-8cf24ecf8339', + 'customizationUuid': '3cd946bb-50e0-40d8-96d3-c9023520b557', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_vPFE_BV..module-2', + 'version': '6', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_vPFE_BV..module-2', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': true, + 'inputs': {} + } + }, + 'volumeGroups': { + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vRE_BV..module-1': { + 'uuid': '25284168-24bb-4698-8cb4-3f509146eca5', + 'invariantUuid': '7253ff5c-97f0-4b8b-937c-77aeb4d79aa1', + 'customizationUuid': 'f7e7c365-60cf-49a9-9ebf-a1aa11b9d401', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_vRE_BV..module-1', + 'version': '6', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_vRE_BV..module-1', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0} + }, + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vPFE_BV..module-2': { + 'uuid': '0a0dd9d4-31d3-4c3a-ae89-a02f383e6a9a', + 'invariantUuid': 'eff8cc59-53a1-4101-aed7-8cf24ecf8339', + 'customizationUuid': '3cd946bb-50e0-40d8-96d3-c9023520b557', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_vPFE_BV..module-2', + 'version': '6', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_vPFE_BV..module-2', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0} + } + } + } + }, + 'vfModules': { + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vRE_BV..module-1': { + 'uuid': '25284168-24bb-4698-8cb4-3f509146eca5', + 'invariantUuid': '7253ff5c-97f0-4b8b-937c-77aeb4d79aa1', + 'customizationUuid': 'f7e7c365-60cf-49a9-9ebf-a1aa11b9d401', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_vRE_BV..module-1', + 'version': '6', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_vRE_BV..module-1', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': true + }, + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_base_vPE_BV..module-0': { + 'uuid': 'f8360508-3f17-4414-a2ed-6bc71161e8db', + 'invariantUuid': 'b34833bb-6aa9-4ad6-a831-70b06367a091', + 'customizationUuid': 'a55961b2-2065-4ab0-a5b7-2fcee1c227e3', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_base_vPE_BV..module-0', + 'version': '5', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_base_vPE_BV..module-0', + 'properties': {'minCountInstances': 1, 'maxCountInstances': 1, 'initialCount': 1}, + 'commands': {}, + 'volumeGroupAllowed': false + }, + '2017488_adiodvpe0..2017488AdiodVpe..ADIOD_vPFE_BV..module-2': { + 'uuid': '0a0dd9d4-31d3-4c3a-ae89-a02f383e6a9a', + 'invariantUuid': 'eff8cc59-53a1-4101-aed7-8cf24ecf8339', + 'customizationUuid': '3cd946bb-50e0-40d8-96d3-c9023520b557', + 'description': null, + 'name': '2017488AdiodVpe..ADIOD_vPFE_BV..module-2', + 'version': '6', + 'modelCustomizationName': '2017488AdiodVpe..ADIOD_vPFE_BV..module-2', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': true + } + }, + 'networks': {}, + 'collectionResource': {}, + 'configurations': {}, + 'serviceProxies': {}, + 'pnfs': {} + } + )) + } + +}); |