diff options
Diffstat (limited to 'vid-webpack-master/src/app/drawingBoard')
16 files changed, 1980 insertions, 0 deletions
diff --git a/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.html b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.html new file mode 100644 index 000000000..5eb977325 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.html @@ -0,0 +1,35 @@ +<div class="available-models-tree"> + <h5> + <span>SERVICE MODEL:</span> + <span id="service-model-name">{{service | serviceInfo: _store: serviceModelId : 'name'}}</span> + </h5> + <div class="available-models-content-wrapper"> + <div class="search-container"> + <input [attr.data-tests-id]="'search-left-tree'" #filter (keyup)="searchTree(filter.value, $event)" placeholder="Filter..."/> + <span class="icon-search"></span> + </div> + + <tree-root #tree [attr.data-tests-id]="'available-models-tree'" [nodes]="nodes" [options]="options"> + <ng-template #treeNodeTemplate let-node let-index="index"> + <div [attr.data-tests-id]="'node-'+node.data.name" (click)="selectNode(node)" [ngClass]="{'selected': index , 'isParent': node.data.type !== 'VFmodule' , 'isChild': node.data.type === 'VFmodule' }"> + <span class="vf-type">{{node.data.type.substring(0,1)}}</span> + <span class="span-name" [innerHTML]=" isFilterEnabled ? (node.data.name | highlight : filter.value) : (node.data.name)"></span> + <span class="actions"> + <span class="number-button" *ngIf="isShowNodeCount(node)"> + <span>{{getNodeCount(node)}}</span> + </span> + <span class="icon-v" *ngIf="isShowIconV(node)"> + <span ></span> + </span> + <span class="icon-plus" *ngIf="isShowIconAdd(node)"> + <span tooltip="Add" [attr.data-tests-id]="'node-'+node.data.name+'-add-btn'" (click)="onClickAdd($event,node)"> + <i class="fa fa-plus-circle" aria-hidden="true"></i> + </span> + </span> + </span> + </div> + </ng-template> + </tree-root> + + </div> +</div> diff --git a/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.scss b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.scss new file mode 100644 index 000000000..44f94109a --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.scss @@ -0,0 +1,398 @@ +available-models-tree { + .available-models-tree { + height: 100%; + display: flex; + flex-direction: column; + line-height: 14px; + border-right: #D2D2D2 1.5px solid; + min-width: 340px; + h5 { + font-family: OpenSans-Semibold; + color: #5A5A5A; + background-color: #F2F2F2; + margin: 0; + padding: 15px; + padding-left: 20px; + span { + vertical-align: middle; + &:first-child { + font-size: 12px; + color: #191919; + } + } + } + .available-models-content-wrapper { + flex: 1; + display: flex; + flex-direction: column; + padding: 20px; + background-color: #F2F2F2; + .search-container { + margin-bottom: 30px; + width: 100%; + display: flex; + background: #FFFFFF; + border: 1px solid #D2D2D2; + border-radius: 2px; + height: 40px; + min-width: 40px; + font-family: OpenSans-Italic; + color: #959595; + input { + flex: 1; + border: 0; + padding-left: 10px; + outline: 0; + } + .icon-search { + display: flex; + width: 40px; + &:after { + content: "\e92e"; + cursor: pointer; + font-size: 20px; + font-weight: 600; + text-align: center; + display: inline-block; + flex: auto; + align-self: center; + } + } + } + tree-root { + flex: 1; + display: flex; + } + tree-viewport { + flex: 1; + height: auto; + overflow: auto; + padding-top: 5px; + .tree-node { + color: #5A5A5A; + font-size: 13px; + white-space: normal; + word-break: break-all; + tree-node-drop-slot { + .node-drop-slot { + display: none; + } + } + &.tree-node-disabled { + color: #D2D2D2; + cursor: default; + pointer-events: none; + } + &:not(.tree-node-disabled) { + >tree-node-wrapper { + .node-wrapper:hover { + color: #009FDB; + .node-content-wrapper { + tree-node-content { + > div { + span.actions { + .number-button { + span { + //background-color: #009FDB; + } + } + .icon-plus span:before { + display: inline-block; + color: #5A5A5A; + } + } + } + } + } + } + } + } + &.tree-node-focused:not(.tree-node-disabled) { + & > tree-node-wrapper { + .node-wrapper { + color: #009FDB; + .node-content-wrapper-focused, + .node-content-wrapper:hover { + background: none; + box-shadow: none; + tree-node-content { + > div { + span.actions { + .number-button { + span { + //background-color: #009FDB; + } + } + } + } + } + } + } + } + } + tree-node-wrapper { + .node-wrapper { + height: 36px; + tree-node-expander { + font-family: 'icomoon' !important; + height: 100%; + .toggle-children-wrapper { + padding: 0; + display: block; + height: 100%; + span.toggle-children { + display: flex; + width: 20px; + top: 0; + height: inherit; + background-image: none; + &:before { + content: "\e900"; + font-weight: 600; + text-align: center; + display: inline-block; + flex: auto; + align-self: center; + font-size: 20px; + } + } + } + .toggle-children-wrapper-expanded { + span.toggle-children { + transform: none; + &:before { + content: "\e930"; + } + } + } + .toggle-children-placeholder { + width: 20px; + } + } + .node-content-wrapper { + padding: 0; + background: none; + box-shadow: none; + height: 100%; + flex: 1; + min-width: 0; + border-left: 1px solid #D2D2D2; + tree-node-content { + > div { + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding-left: 10px; + span { + &.actions { + height: 100%; + display: flex; + justify-content: space-between; + align-items: center; + >span { + width: 45px; + max-width: 45px; + text-align: center; + } + .number-button { + width: 30px; + padding-left: 0; + text-align: center; + span { + display: block; + font-size: 11px; + } + } + .icon-v { + width: 45px; + span:before { + content: "\e932"; + color: #5A5A5A; + font-size: 16px; + text-align: center; + display: inline-block; + vertical-align: baseline; + } + } + .icon-plus { + width: 45px; + span { + &:before { + //content: "\e901"; + //fill:#009FDB; + //color: #009FDB; + //font-size: 16px; + //text-align: center; + //display: none; + //vertical-align: baseline; + } + &:hover:before { + //color: #009FDB !important; + + } + } + } + } + } + } + } + } + + } + } + tree-node-children { + .tree-children { + padding-left: 20px; + } + } + } + } + + } + } +} +.highlight { + background-color: #9DD9EF; +} + +#drawing-board-tree{ + .tree-node.tree-node-expanded.tree-node-focused { + border: 1px solid #009FDB; + } + +} + +available-models-tree { + + .tree-root { + margin-top: 35px; + } + + tree-node-expander { + background: #FFFFFF; + border: 1px solid #D2D2D2; + border-right: none; + width: 45px; + padding-left: 12px; + } + + .node-content-wrapper { + border: none; + } + + tree-node-wrapper tree-node-expander{ + background: none !important; + border: none !important; + } + + tree-node-content div { + background: white; + } + + .node-wrapper { + height: 45px !important; + background: #FFFFFF; + border: 1px solid #D2D2D2; + } + + tree-node-collection div { + margin-top: 0px; + } + + .tree-node-leaf .node-wrapper tree-node-expander { + display: none; + } + + .tree-children { + padding: 20px; + } + + .tree-node.tree-node-expanded.tree-node-focused { + border: 1px solid #009FDB; + } + + .tree-node.tree-node-expanded { + border: 1px solid rgba(128, 128, 128, 0.72); + margin-bottom: 10px; + } + + .tree-children { + padding-left: 0; + } + + tree-node-content .actions .number-button { + height: 45px; + padding-top: 14px; + border: 1px solid #D2D2D2; + padding-left: 0; + span { + background: none; + font-size: 11px; + color: #5A5A5A; + } + } + + + + .node-content-wrapper.node-content-wrapper-focused tree-node-content div{ + background: #009FDB !important; + color: white; + + .isParent { + border-left: 1px solid #009FDB; + } + + .number-button span{ + color: white !important; + } + + .icon-v span:before{ + color: white !important; + } + } + + .vf-type { + width: 20px; + height: 45px; + padding-top: 16px; + border-right: 1px solid #D2D2D2; + + } + + .isParent { + width: 100%; + padding-left: 5px; + } + + .tree-node-expanded .isChild .vf-type { + display: none; + } + + .isParent .span-name { + width: 100%; + padding-left: 10px; + } + + .toggle-children-wrapper.toggle-children-wrapper-expanded { + .toggle-children:before { + color: #009FDB; + } + } + + .tree-node.tree-node-expanded .tree-children { + border: 1px solid rgba(128, 128, 128, 0.72); + } + + .tree-node.tree-node-expanded.tree-node-focused .tree-children { + border: 1px solid #009fdb; + } + + .tree-node-leaf .node-wrapper{ + margin-left: 45px; + border-left: none; + } +} + + + diff --git a/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.ts b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.ts new file mode 100644 index 000000000..4e5819e4c --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.ts @@ -0,0 +1,166 @@ +import {Component, EventEmitter, Output, ViewChild} from '@angular/core'; +import {ITreeOptions, TreeComponent} from 'angular-tree-component'; +import '../../../../node_modules/angular-tree-component/dist/angular-tree-component.css'; +import {IDType, ITreeNode} from 'angular-tree-component/dist/defs/api'; +import {DialogService} from 'ng2-bootstrap-modal'; +import {AvailableModelsTreeService} from './available-models-tree.service'; +import {NgRedux} from "@angular-redux/store"; +import {ActivatedRoute} from '@angular/router'; +import {AppState} from '../../store/reducers'; +import {AaiService} from '../../services/aaiService/aai.service'; +import {ServicePlanningService} from '../../services/service-planning.service'; +import {VnfPopupComponent} from '../../components/vnf-popup/vnf-popup.components'; +import {ServiceNodeTypes} from '../../shared/models/ServiceNodeTypes'; +import {VfModuleMap} from '../../shared/models/vfModulesMap'; +import {IframeService} from "../../shared/utils/iframe.service"; +import {createVFModuleInstance} from "../../service.actions"; +import {DefaultDataGeneratorService} from "../../shared/services/defaultDataServiceGenerator/default.data.generator.service"; + + +@Component({ + selector: 'available-models-tree', + templateUrl: './available-models-tree.component.html', + styleUrls: ['./available-models-tree.component.scss'] +}) + + +export class AvailableModelsTreeComponent{ + + serviceModelId: string; + serviceHierarchy; + parentElementClassName = 'content'; + _store : NgRedux<AppState>; + constructor(private _servicePlanningService: ServicePlanningService, + private _iframeService: IframeService, + private _aaiService: AaiService, + private route: ActivatedRoute, + private dialogService: DialogService, + private _availableModelsTreeService: AvailableModelsTreeService, + private _defaultDataGeneratorService: DefaultDataGeneratorService, + private store: NgRedux<AppState>) { + this._store = store; + this.route + .queryParams + .subscribe(params => { + this.serviceModelId = params['serviceModelId']; + this._aaiService.getServiceModelById(this.serviceModelId).subscribe( + value => { + this.serviceHierarchy = value; + this.nodes = this._servicePlanningService.convertServiceModelToTreeNodes(this.serviceHierarchy); + }, + error => { + console.log('error is ', error) + } + ); + }); + + } + + @Output() + highlightInstances: EventEmitter<number> = new EventEmitter<number>(); + @ViewChild('tree') tree: TreeComponent; + + nodes = []; + service = {name: ''}; + isFilterEnabled: boolean = false; + + options: ITreeOptions = { + nodeHeight: 36, + dropSlotHeight: 0, + nodeClass: (node: ITreeNode) => { + if(node.data.type === ServiceNodeTypes.VFmodule && !this.getNodeCount(node.parent)) + { + node.data.disabled = true; + return 'tree-node tree-node-disabled'; + } + node.data.disabled = false; + return 'tree-node'; + } + }; + + expandParentByNodeId(id: IDType): void { + this.tree.treeModel.getNodeById(id).parent.expand(); + } + + searchTree(searchText: string, event: KeyboardEvent): void { + if (searchText === '') { + return; + } + this.isFilterEnabled = event.key === 'Delete' || event.key === 'Backspace' || searchText.length > 1; + if (this.isFilterEnabled) { + let __this = this; + let results: ITreeNode[] = []; + this.nodes.forEach(function (node) { + __this.searchTreeNode(node, searchText, results); + }); + results.forEach(function (result) { + __this.expandParentByNodeId(result.id) + }); + } + } + + searchTreeNode(node, searchText: string, results): void { + if (node.name.toLowerCase().indexOf(searchText.toLowerCase()) != -1) { + results.push(node); + } + if (node.children != null) { + for (let i = 0; i < node.children.length; i++) { + this.searchTreeNode(node.children[i], searchText, results); + } + } + } + + selectNode(node: ITreeNode): void { + node.expand(); + this.highlightInstances.emit(node.data.id); + } + + onClickAdd(e: MouseEvent, node: ITreeNode): void { + let data = node.data; + let dynamicInputs = data.dynamicInputs; + let userProvidedNaming:boolean = data.userProvidedNaming; + let type:string = data.type; + if(!this.store.getState().global.flags['FLAG_SETTING_DEFAULTS_IN_DRAWING_BOARD']|| node.data.type === ServiceNodeTypes.VF || this._availableModelsTreeService.shouldOpenDialog(type, dynamicInputs, userProvidedNaming)) { + this._iframeService.addClassOpenModal(this.parentElementClassName); + this.dialogService.addDialog(VnfPopupComponent, { + serviceModelId: this.serviceModelId, + parentModelName: node.parent && node.parent.data.name, + modelName: data.name, + modelType: type, + dynamicInputs: dynamicInputs, + userProvidedNaming: userProvidedNaming, + isNewVfModule : true + }); + } + else { + let vfModule = this._defaultDataGeneratorService.generateVFModule(this.serviceHierarchy, node.parent.data.name, node.data.name); + this.store.dispatch(createVFModuleInstance(vfModule, node.data.name, this.serviceModelId)); + } + e.preventDefault(); + e.stopPropagation(); + } + + getNodeCount(node: ITreeNode): number { + let modelName: string = node.data.name; + if (ServicePlanningService.isVfModule(node)) { + let parentVnfModelName = node.parent.data.name; + let vfModuleMap: VfModuleMap = this._servicePlanningService.getVfModuleMap(this.serviceModelId, parentVnfModelName, modelName); + return vfModuleMap ? Object.keys(vfModuleMap).length : 0; + } else if (ServicePlanningService.isVnf(node)) { + let vnfInstance = this._servicePlanningService.getVnfInstance(this.serviceModelId, modelName); + return vnfInstance ? 1 : 0; + } + } + + isShowIconV(node: ITreeNode): boolean { + return this.getNodeCount(node) > 0; + } + + isShowNodeCount(node: ITreeNode): boolean { + return this.getNodeCount(node) > 0; + } + + isShowIconAdd(node: ITreeNode): boolean { + return this._availableModelsTreeService.shouldShowAddIcon(node, this.store.getState().service.serviceHierarchy, this.serviceModelId, this.getNodeCount(node)); + } +} diff --git a/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.spec.ts b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.spec.ts new file mode 100644 index 000000000..10cbb0d8f --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.spec.ts @@ -0,0 +1,450 @@ +import {TestBed, getTestBed} from '@angular/core/testing'; +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import {AvailableModelsTreeService} from './available-models-tree.service'; +import {ServicePlanningService} from "../../services/service-planning.service"; +import {ServiceNodeTypes} from "../../shared/models/ServiceNodeTypes"; +import {NgRedux} from "@angular-redux/store"; +import {MockAppStore} from "../../services/service-planning.service.spec"; + +describe('Available Models Tree Service', () => { + let injector; + let service: AvailableModelsTreeService; + let httpMock: HttpTestingController; + + beforeEach(() => { + + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [AvailableModelsTreeService, + ServicePlanningService, + {provide: NgRedux, useClass: MockAppStore}] + }); + + injector = getTestBed(); + service = injector.get(AvailableModelsTreeService); + httpMock = injector.get(HttpTestingController); + }); + + describe('#shouldShowAddIcon', () => { + it('should return true if number of current vnf modules is under the max', (done: DoneFn) => { + let treeNode = { + data: { + children: [], + name: 'vf_vmee0..VfVmee..base_vmme..module-0' + } + }; + + let serviceHierarchy = getSericeServiceHierarchy(); + let result = service.shouldShowAddIcon(treeNode, serviceHierarchy, '6e59c5de-f052-46fa-aa7e-2fca9d674c44', 0); + expect(result).toBeTruthy(); + done(); + }); + + it('should return false if number of current vnf modules are more than max', (done: DoneFn) => { + let treeNode = { + data: { + children: [], + name: 'vf_vmee0..VfVmee..base_vmme..module-0' + } + }; + + let serviceHierarchy = getSericeServiceHierarchy(); + let result = service.shouldShowAddIcon(treeNode, serviceHierarchy, '6e59c5de-f052-46fa-aa7e-2fca9d674c44', 2); + expect(result).toBeFalsy(); + done(); + }); + + it('should return true if number of current vnf modules are more than max and max is null', (done: DoneFn) => { + let treeNode = { + data: { + children: [], + name: 'vf_vmee0..VfVmee..base_vmme..module-0' + } + }; + + let serviceHierarchy = getSericeServiceHierarchy(); + let result = service.shouldShowAddIcon(treeNode, serviceHierarchy, '6e59c5de-f052-46fa-aa7e-2fca9d674c44', 0); + expect(result).toBeTruthy(); + done(); + }); + }); + + describe('#shouldOpenModalDialogOnAddInstance', () => { + let serviceHierarchy = getSericeServiceHierarchy(); + + it('should open popup on add instance', (done: DoneFn) => { + // add vnf should return true + let result = service.shouldOpenDialog(ServiceNodeTypes.VF, [], false); + expect(result).toBeTruthy(); + + // add vfModule with user provided naming should return true + result = service.shouldOpenDialog(ServiceNodeTypes.VFmodule, [], true); + expect(result).toBeTruthy(); + + // add vfModule with dynamicInputs without defaultValues should return true + result = service.shouldOpenDialog(ServiceNodeTypes.VFmodule, [{ + id: '2017488_adiodvpe0_vnf_config_template_version', + type: 'string', + name: '2017488_adiodvpe0_vnf_config_template_version', + isRequired: true, + description: 'VPE Software Version' + }], false); + expect(result).toBeTruthy(); + + // add vfModule with dynamicInputs with defaultValues should return false + result = service.shouldOpenDialog(ServiceNodeTypes.VFmodule, [{ + id: '2017488_adiodvpe0_vnf_config_template_version', + type: 'string', + name: '2017488_adiodvpe0_vnf_config_template_version', + value: '17.2', + isRequired: true, + description: 'VPE Software Version' + }], false); + expect(result).toBeFalsy(); + done(); + }); + }); + + function getSericeServiceHierarchy() { + return JSON.parse(JSON.stringify( + { + '6e59c5de-f052-46fa-aa7e-2fca9d674c44': { + 'service': { + 'uuid': '6e59c5de-f052-46fa-aa7e-2fca9d674c44', + 'invariantUuid': 'e49fbd11-e60c-4a8e-b4bf-30fbe8f4fcc0', + 'name': 'ComplexService', + 'version': '1.0', + 'toscaModelURL': null, + 'category': 'Mobility', + 'serviceType': '', + 'serviceRole': '', + 'description': 'ComplexService', + 'serviceEcompNaming': 'true', + 'instantiationType': 'Macro', + 'inputs': {} + }, + 'vnfs': { + 'VF_vMee 0': { + 'uuid': 'd6557200-ecf2-4641-8094-5393ae3aae60', + 'invariantUuid': '4160458e-f648-4b30-a176-43881ffffe9e', + 'description': 'VSP_vMee', + 'name': 'VF_vMee', + 'version': '2.0', + 'customizationUuid': '91415b44-753d-494c-926a-456a9172bbb9', + 'inputs': {}, + 'commands': {}, + 'properties': { + 'gpb2_Internal2_mac': '00:80:37:0E:02:22', + 'sctp-b-ipv6-egress_src_start_port': '0', + 'sctp-a-ipv6-egress_rule_application': 'any', + 'Internal2_allow_transit': 'true', + 'sctp-b-IPv6_ethertype': 'IPv6', + 'sctp-a-egress_rule_application': 'any', + 'sctp-b-ingress_action': 'pass', + 'sctp-b-ingress_rule_protocol': 'icmp', + 'ncb2_Internal1_mac': '00:80:37:0E:0F:12', + 'sctp-b-ipv6-ingress-src_start_port': '0.0', + 'ncb1_Internal2_mac': '00:80:37:0E:09:12', + 'fsb_volume_size_0': '320.0', + 'sctp-b-egress_src_addresses': 'local', + 'sctp-a-ipv6-ingress_ethertype': 'IPv4', + 'sctp-a-ipv6-ingress-dst_start_port': '0', + 'sctp-b-ipv6-ingress_rule_application': 'any', + 'domain_name': 'default-domain', + 'sctp-a-ingress_rule_protocol': 'icmp', + 'sctp-b-egress-src_start_port': '0.0', + 'sctp-a-egress_src_addresses': 'local', + 'sctp-b-display_name': 'epc-sctp-b-ipv4v6-sec-group', + 'sctp-a-egress-src_start_port': '0.0', + 'sctp-a-ingress_ethertype': 'IPv4', + 'sctp-b-ipv6-ingress-dst_end_port': '65535', + 'sctp-b-dst_subnet_prefix_v6': '::', + 'nf_naming': '{ecomp_generated_naming=true}', + 'sctp-a-ipv6-ingress_src_subnet_prefix': '0.0.0.0', + 'sctp-b-egress-dst_start_port': '0.0', + 'ncb_flavor_name': 'nv.c20r64d1', + 'gpb1_Internal1_mac': '00:80:37:0E:01:22', + 'sctp-b-egress_dst_subnet_prefix_len': '0.0', + 'Internal2_net_cidr': '169.255.0.0', + 'sctp-a-ingress-dst_start_port': '0.0', + 'sctp-a-egress-dst_start_port': '0.0', + 'fsb1_Internal2_mac': '00:80:37:0E:0B:12', + 'sctp-a-egress_ethertype': 'IPv4', + 'vlc_st_service_mode': 'in-network-nat', + 'sctp-a-ipv6-egress_ethertype': 'IPv4', + 'sctp-a-egress-src_end_port': '65535.0', + 'sctp-b-ipv6-egress_rule_application': 'any', + 'sctp-b-egress_action': 'pass', + 'sctp-a-ingress-src_subnet_prefix_len': '0.0', + 'sctp-b-ipv6-ingress-src_end_port': '65535.0', + 'sctp-b-name': 'epc-sctp-b-ipv4v6-sec-group', + 'fsb2_Internal1_mac': '00:80:37:0E:0D:12', + 'sctp-a-ipv6-ingress-src_start_port': '0.0', + 'sctp-b-ipv6-egress_ethertype': 'IPv4', + 'Internal1_net_cidr': '169.253.0.0', + 'sctp-a-egress_dst_subnet_prefix': '0.0.0.0', + 'fsb_flavor_name': 'nv.c20r64d1', + 'sctp_rule_protocol': '132', + 'sctp-b-ipv6-ingress_src_subnet_prefix_len': '0', + 'sctp-a-ipv6-ingress_rule_application': 'any', + 'sctp-a-IPv6_ethertype': 'IPv6', + 'vlc2_Internal1_mac': '00:80:37:0E:02:12', + 'vlc_st_virtualization_type': 'virtual-machine', + 'sctp-b-ingress-dst_start_port': '0.0', + 'sctp-b-ingress-dst_end_port': '65535.0', + 'sctp-a-ipv6-ingress-src_end_port': '65535.0', + 'sctp-a-display_name': 'epc-sctp-a-ipv4v6-sec-group', + 'sctp-b-ingress_rule_application': 'any', + 'int2_sec_group_name': 'int2-sec-group', + 'vlc_flavor_name': 'nd.c16r64d1', + 'sctp-b-ipv6-egress_src_addresses': 'local', + 'vlc_st_interface_type_int1': 'other1', + 'sctp-b-egress-src_end_port': '65535.0', + 'sctp-a-ipv6-egress-dst_start_port': '0', + 'vlc_st_interface_type_int2': 'other2', + 'sctp-a-ipv6-egress_rule_protocol': 'any', + 'Internal2_shared': 'false', + 'sctp-a-ipv6-egress_dst_subnet_prefix_len': '0', + 'Internal2_rpf': 'disable', + 'vlc1_Internal1_mac': '00:80:37:0E:01:12', + 'sctp-b-ipv6-egress_src_end_port': '65535', + 'sctp-a-ipv6-egress_src_addresses': 'local', + 'sctp-a-ingress-dst_end_port': '65535.0', + 'sctp-a-ipv6-egress_src_end_port': '65535', + 'Internal1_forwarding_mode': 'l2', + 'Internal2_dhcp': 'false', + 'sctp-a-dst_subnet_prefix_v6': '::', + 'pxe_image_name': 'MME_PXE-Boot_16ACP04_GA.qcow2', + 'vlc_st_interface_type_gtp': 'other0', + 'ncb1_Internal1_mac': '00:80:37:0E:09:12', + 'sctp-b-src_subnet_prefix_v6': '::', + 'sctp-a-egress_dst_subnet_prefix_len': '0.0', + 'int1_sec_group_name': 'int1-sec-group', + 'Internal1_dhcp': 'false', + 'sctp-a-ipv6-egress_dst_end_port': '65535', + 'Internal2_forwarding_mode': 'l2', + 'fsb2_Internal2_mac': '00:80:37:0E:0D:12', + 'sctp-b-egress_dst_subnet_prefix': '0.0.0.0', + 'Internal1_net_cidr_len': '17', + 'gpb2_Internal1_mac': '00:80:37:0E:02:22', + 'sctp-b-ingress-src_subnet_prefix_len': '0.0', + 'sctp-a-ingress_dst_addresses': 'local', + 'sctp-a-egress_action': 'pass', + 'fsb_volume_type_0': 'SF-Default-SSD', + 'ncb2_Internal2_mac': '00:80:37:0E:0F:12', + 'vlc_st_interface_type_sctp_a': 'left', + 'vlc_st_interface_type_sctp_b': 'right', + 'sctp-a-src_subnet_prefix_v6': '::', + 'vlc_st_version': '2', + 'sctp-b-egress_ethertype': 'IPv4', + 'sctp-a-ingress_rule_application': 'any', + 'gpb1_Internal2_mac': '00:80:37:0E:01:22', + 'instance_ip_family_v6': 'v6', + 'sctp-a-ipv6-egress_src_start_port': '0', + 'sctp-b-ingress-src_start_port': '0.0', + 'sctp-b-ingress_dst_addresses': 'local', + 'fsb1_Internal1_mac': '00:80:37:0E:0B:12', + 'vlc_st_interface_type_oam': 'management', + 'multi_stage_design': 'false', + 'oam_sec_group_name': 'oam-sec-group', + 'Internal2_net_gateway': '169.255.0.3', + 'sctp-a-ipv6-ingress-dst_end_port': '65535', + 'sctp-b-ipv6-egress-dst_start_port': '0', + 'Internal1_net_gateway': '169.253.0.3', + 'sctp-b-ipv6-egress_rule_protocol': 'any', + 'gtp_sec_group_name': 'gtp-sec-group', + 'sctp-a-ipv6-egress_dst_subnet_prefix': '0.0.0.0', + 'sctp-b-ipv6-egress_dst_subnet_prefix_len': '0', + 'sctp-a-ipv6-ingress_dst_addresses': 'local', + 'sctp-a-egress_rule_protocol': 'icmp', + 'sctp-b-ipv6-egress_action': 'pass', + 'sctp-a-ipv6-egress_action': 'pass', + 'Internal1_shared': 'false', + 'sctp-b-ipv6-ingress_rule_protocol': 'any', + 'Internal2_net_cidr_len': '17', + 'sctp-a-name': 'epc-sctp-a-ipv4v6-sec-group', + 'sctp-a-ingress-src_end_port': '65535.0', + 'sctp-b-ipv6-ingress_src_subnet_prefix': '0.0.0.0', + 'sctp-a-egress-dst_end_port': '65535.0', + 'sctp-a-ingress_action': 'pass', + 'sctp-b-egress_rule_protocol': 'icmp', + 'sctp-b-ipv6-ingress_action': 'pass', + 'vlc_st_service_type': 'firewall', + 'sctp-b-ipv6-egress_dst_end_port': '65535', + 'sctp-b-ipv6-ingress-dst_start_port': '0', + 'vlc2_Internal2_mac': '00:80:37:0E:02:12', + 'vlc_st_availability_zone': 'true', + 'fsb_volume_image_name_1': 'MME_FSB2_16ACP04_GA.qcow2', + 'sctp-b-ingress-src_subnet_prefix': '0.0.0.0', + 'sctp-a-ipv6-ingress_src_subnet_prefix_len': '0', + 'Internal1_allow_transit': 'true', + 'gpb_flavor_name': 'nv.c20r64d1', + 'availability_zone_max_count': '1', + 'fsb_volume_image_name_0': 'MME_FSB1_16ACP04_GA.qcow2', + 'sctp-b-ipv6-ingress_dst_addresses': 'local', + 'sctp-b-ipv6-egress_dst_subnet_prefix': '0.0.0.0', + 'sctp-b-ipv6-ingress_ethertype': 'IPv4', + 'vlc1_Internal2_mac': '00:80:37:0E:01:12', + 'sctp-a-ingress-src_subnet_prefix': '0.0.0.0', + 'sctp-a-ipv6-ingress_action': 'pass', + 'Internal1_rpf': 'disable', + 'sctp-b-ingress_ethertype': 'IPv4', + 'sctp-b-egress_rule_application': 'any', + 'sctp-b-ingress-src_end_port': '65535.0', + 'sctp-a-ipv6-ingress_rule_protocol': 'any', + 'sctp-a-ingress-src_start_port': '0.0', + 'sctp-b-egress-dst_end_port': '65535.0' + }, + 'type': 'VF', + 'modelCustomizationName': 'VF_vMee 0', + 'vfModules': { + 'vf_vmee0..VfVmee..vmme_vlc..module-1': { + 'uuid': '522159d5-d6e0-4c2a-aa44-5a542a12a830', + 'invariantUuid': '98a7c88b-b577-476a-90e4-e25a5871e02b', + 'customizationUuid': '55b1be94-671a-403e-a26c-667e9c47d091', + 'description': null, + 'name': 'VfVmee..vmme_vlc..module-1', + 'version': '2', + 'modelCustomizationName': 'VfVmee..vmme_vlc..module-1', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': false + }, + 'vf_vmee0..VfVmee..vmme_gpb..module-2': { + 'uuid': '41708296-e443-4c71-953f-d9a010f059e1', + 'invariantUuid': '1cca90b8-3490-495e-87da-3f3e4c57d5b9', + 'customizationUuid': '6add59e0-7fe1-4bc4-af48-f8812422ae7c', + 'description': null, + 'name': 'VfVmee..vmme_gpb..module-2', + 'version': '2', + 'modelCustomizationName': 'VfVmee..vmme_gpb..module-2', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': false + }, + 'vf_vmee0..VfVmee..base_vmme..module-0': { + 'uuid': 'a27f5cfc-7f12-4f99-af08-0af9c3885c87', + 'invariantUuid': 'a6f9e51a-2b35-416a-ae15-15e58d61f36d', + 'customizationUuid': 'f8c040f1-7e51-4a11-aca8-acf256cfd861', + 'description': null, + 'name': 'VfVmee..base_vmme..module-0', + 'version': '2', + 'modelCustomizationName': 'VfVmee..base_vmme..module-0', + 'properties': {'minCountInstances': 1, 'maxCountInstances': 1, 'initialCount': 1}, + 'commands': {}, + 'volumeGroupAllowed': true + } + }, + 'volumeGroups': { + 'vf_vmee0..VfVmee..base_vmme..module-0': { + 'uuid': 'a27f5cfc-7f12-4f99-af08-0af9c3885c87', + 'invariantUuid': 'a6f9e51a-2b35-416a-ae15-15e58d61f36d', + 'customizationUuid': 'f8c040f1-7e51-4a11-aca8-acf256cfd861', + 'description': null, + 'name': 'VfVmee..base_vmme..module-0', + 'version': '2', + 'modelCustomizationName': 'VfVmee..base_vmme..module-0', + 'properties': {'minCountInstances': 1, 'maxCountInstances': 1, 'initialCount': 1} + } + } + } + }, + 'networks': { + 'ExtVL 0': { + 'uuid': 'ddc3f20c-08b5-40fd-af72-c6d14636b986', + 'invariantUuid': '379f816b-a7aa-422f-be30-17114ff50b7c', + 'description': 'ECOMP generic virtual link (network) base type for all other service-level and global networks', + 'name': 'ExtVL', + 'version': '37.0', + 'customizationUuid': '94fdd893-4a36-4d70-b16a-ec29c54c184f', + 'inputs': {}, + 'commands': {}, + 'properties': { + 'network_assignments': '{is_external_network=false, ipv4_subnet_default_assignment={min_subnets_count=1}, ecomp_generated_network_assignment=false, ipv6_subnet_default_assignment={min_subnets_count=1}}', + 'exVL_naming': '{ecomp_generated_naming=true}', + 'network_flows': '{is_network_policy=false, is_bound_to_vpn=false}', + 'network_homing': '{ecomp_selected_instance_node_target=false}' + }, + 'type': 'VL', + 'modelCustomizationName': 'ExtVL 0' + } + }, + 'configurations': { + 'Port Mirroring Configuration By Policy 0': { + 'uuid': 'b4398538-e89d-4f13-b33d-ca323434ba50', + 'invariantUuid': '6ef0ca40-f366-4897-951f-abd65d25f6f7', + 'description': 'A port mirroring configuration by policy object', + 'name': 'Port Mirroring Configuration By Policy', + 'version': '27.0', + 'customizationUuid': '3c3b7b8d-8669-4b3b-8664-61970041fad2', + 'inputs': {}, + 'commands': {}, + 'properties': {}, + 'type': 'Configuration', + 'modelCustomizationName': 'Port Mirroring Configuration By Policy 0', + 'sourceNodes': [], + 'collectorNodes': null, + 'configurationByPolicy': false + } + }, + 'serviceProxies': {}, + 'vfModules': { + 'vf_vmee0..VfVmee..vmme_vlc..module-1': { + 'uuid': '522159d5-d6e0-4c2a-aa44-5a542a12a830', + 'invariantUuid': '98a7c88b-b577-476a-90e4-e25a5871e02b', + 'customizationUuid': '55b1be94-671a-403e-a26c-667e9c47d091', + 'description': null, + 'name': 'VfVmee..vmme_vlc..module-1', + 'version': '2', + 'modelCustomizationName': 'VfVmee..vmme_vlc..module-1', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': false + }, + 'vf_vmee0..VfVmee..vmme_gpb..module-2': { + 'uuid': '41708296-e443-4c71-953f-d9a010f059e1', + 'invariantUuid': '1cca90b8-3490-495e-87da-3f3e4c57d5b9', + 'customizationUuid': '6add59e0-7fe1-4bc4-af48-f8812422ae7c', + 'description': null, + 'name': 'VfVmee..vmme_gpb..module-2', + 'version': '2', + 'modelCustomizationName': 'VfVmee..vmme_gpb..module-2', + 'properties': {'minCountInstances': 0, 'maxCountInstances': null, 'initialCount': 0}, + 'commands': {}, + 'volumeGroupAllowed': false + }, + 'vf_vmee0..VfVmee..base_vmme..module-0': { + 'uuid': 'a27f5cfc-7f12-4f99-af08-0af9c3885c87', + 'invariantUuid': 'a6f9e51a-2b35-416a-ae15-15e58d61f36d', + 'customizationUuid': 'f8c040f1-7e51-4a11-aca8-acf256cfd861', + 'description': null, + 'name': 'VfVmee..base_vmme..module-0', + 'version': '2', + 'modelCustomizationName': 'VfVmee..base_vmme..module-0', + 'properties': {'minCountInstances': 1, 'maxCountInstances': 1, 'initialCount': 1}, + 'commands': {}, + 'volumeGroupAllowed': true + } + }, + 'volumeGroups': { + 'vf_vmee0..VfVmee..base_vmme..module-0': { + 'uuid': 'a27f5cfc-7f12-4f99-af08-0af9c3885c87', + 'invariantUuid': 'a6f9e51a-2b35-416a-ae15-15e58d61f36d', + 'customizationUuid': 'f8c040f1-7e51-4a11-aca8-acf256cfd861', + 'description': null, + 'name': 'VfVmee..base_vmme..module-0', + 'version': '2', + 'modelCustomizationName': 'VfVmee..base_vmme..module-0', + 'properties': {'minCountInstances': 1, 'maxCountInstances': 1, 'initialCount': 1} + } + }, + 'pnfs': {} + } + } + )); + } + +}); diff --git a/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.ts b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.ts new file mode 100644 index 000000000..57dc4b409 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.ts @@ -0,0 +1,36 @@ +import {Injectable} from '@angular/core'; +import * as _ from "lodash"; +import {ServicePlanningService} from "../../services/service-planning.service"; + +@Injectable() +export class AvailableModelsTreeService { + constructor(private _servicePlanningService: ServicePlanningService) { + } + + shouldShowAddIcon(node: any, serviceHierarchy: any, serviceModelId: string, currentNodeCount: number): boolean { + let maxNodes: number = 1; + if (node.data.children !== null && node.data.children.length == 0) { + let vnfModules = serviceHierarchy[serviceModelId].vfModules; + if (vnfModules[node.data.name]) { + maxNodes = vnfModules[node.data.name].properties.maxCountInstances || 1; + } + } + return !node.data.disabled && currentNodeCount < maxNodes + } + + shouldOpenDialog(type: string, dynamicInputs: any, userProvidedNaming: boolean): boolean { + if (userProvidedNaming || this._servicePlanningService.requiredFields[type].length > 0) { + return true; + } + + if (dynamicInputs) { + for(let input of dynamicInputs) { + if (input.isRequired && _.isEmpty(input.value)) { + return true; + } + } + } + return false; + } + +} diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.html b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.html new file mode 100644 index 000000000..7d0f7f456 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.html @@ -0,0 +1,33 @@ +<div class="drawing-board-header"> + <div class="left-header"> + <span [attr.data-tests-id]="'backBtn'" class="icon-back" (click)="closePage()"></span> + <span [attr.data-tests-id]="'serviceInstance'" class="service-instance-label">Service instance:</span> + <span [attr.data-tests-id]="'serviceName'" class="service-instance-name">{{serviceName}}</span> + <span class="quantity-container" style=" padding: 10px;font-size: 13px;" tooltip="Number of services to instantiate including all their objects as defined below"> + <span [attr.data-tests-id]="'quantityLabel'" class="quantity-label" >Scale Times:</span> + <span [attr.data-tests-id]="'servicesQuantity'" class="quantity" style="font-family: OpenSans-Semibold;font-size: 14px;"> {{numServicesToDeploy}} </span> + </span> + <span class="service-instance-label">status:</span> + <span [attr.data-tests-id]="'serviceStatus'" class="status">{{status}}</span> + </div> + <div class="right-header"> + <span class="menu-container"> + <span [attr.data-tests-id]="'openMenuBtn'" class="icon-browse" (click)="onContextMenu($event)"></span> + <context-menu> + <ng-template contextMenuItem (execute)="editService()"> + <div [attr.data-tests-id]="'context-menu-header-edit-item'"> + <span class="icon-edit"></span> + Edit + </div> + </ng-template> + <ng-template contextMenuItem (execute)="closePage()"> + <div [attr.data-tests-id]="'context-menu-header-delete-item'"> + <span class="icon-trash"></span> + Delete + </div> + </ng-template> + </context-menu> + </span> + <button [disabled]="false" [attr.data-tests-id]="'deployBtn'" (click)="deployMacroservice()" class="deploy-btn">DEPLOY</button> + </div> +</div> diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.scss b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.scss new file mode 100644 index 000000000..29b7711bc --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.scss @@ -0,0 +1,95 @@ +.drawing-board-header { + height: 56px; + margin-bottom: 4px; + position: relative; + font-family: OpenSans-Regular; + display: flex; + justify-content: space-between; + font-size: 14px; + box-shadow: 2px 2px 6px #D2D2D2; + color: #191919; + [class^="icon-"] { + height: 56px; + width: 56px; + display: flex; + align-items: center; + text-align: center; + color: #5A5A5A; + cursor: pointer; + &:before { + font-size: 18px; + width: 100%; + } + &:hover:before { + color: #009FDB; + } + } + .left-header { + display: flex; + align-items: center; + .icon-back { + border-right: 1px solid #EAEAEA; + &:before { + content: "\e906"; + font-size: 24px; + } + } + .service-instance-label { + padding: 0 5px; + font-family: OpenSans-Regular; + font-size: 13px; + color: #191919; + } + .service-instance-name { + padding-right: 20px; + color: #191919; + font-family: OpenSans-Semibold; + background-color: white; + font-size: 16px + } + .status { + font-family: OpenSans-Semibold; + line-height: 14px; + font-size: 14px; + } + } + .right-header { + display: flex; + align-items: center; + .quantity-container { + .quantity-label { + padding-left: 10px; + font-family: OpenSans-Semibold; + font-size: 12px; + } + .quantity { + padding: 5px 10px 5px 0; + font-family: OpenSans-Semibold; + font-size: 18px; + } + } + [class^="icon-"] { + border-left: 1px solid #EAEAEA; + } + .menu-container { + height: 100%; + display: flex; + background: none; + border: none; + padding: 0; + outline: none; + } + .icon-browse:before { + content: '\e924'; + display: inline-block; + font-size: 24px; + } + .deploy-btn { + color: #FFFFFF ; + background: #009fdb; + width: 128px; + height: 100%; + border: none; + } + } +} diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.ts b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.ts new file mode 100644 index 000000000..38284e214 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.ts @@ -0,0 +1,119 @@ +import {Component, ViewChild} from '@angular/core'; +import {ContextMenuComponent, ContextMenuService} from 'ngx-contextmenu'; +import {DialogService} from 'ng2-bootstrap-modal'; +import {ServicePopupComponent} from '../../components/service-popup/service-popup.component'; +import {MsoService} from '../../services/msoService/mso.service' +import * as _ from 'lodash'; +import {ActivatedRoute} from '@angular/router'; +import {ServiceInstance} from "../../shared/models/serviceInstance"; +import {OwningEntity} from "../../shared/models/owningEntity"; +import {MessageBoxData, ModalSize, ModalType} from "../../shared/components/messageBox/messageBox.data"; +import {MessageBoxService} from "../../shared/components/messageBox/messageBox.service"; +import {NgRedux} from "@angular-redux/store"; +import {AppState} from "../../store/reducers"; +import {IframeService} from "../../shared/utils/iframe.service"; + +@Component({ + selector: 'drawing-board-header', + providers: [MsoService], + templateUrl: './drawing-board-header.component.html', + styleUrls: ['./drawing-board-header.component.scss'] +}) + +export class DrawingBoardHeader { + serviceName: string; + numServicesToDeploy: number; + status: string = 'Designing a new service'; + serviceModelId: string; + parentElementClassName = 'content'; + + constructor(private _contextMenuService: ContextMenuService, private dialogService: DialogService, + private _iframeService : IframeService, + private route: ActivatedRoute, private msoService: MsoService, + private store: NgRedux<AppState>) { + this.route + .queryParams + .subscribe(params => { + this.serviceModelId = params['serviceModelId']; + if (_.has(this.store.getState().service.serviceHierarchy, this.serviceModelId)) { + this.setValuesFromStore(); + this.store.subscribe(() => { + this.setValuesFromStore(); + }); + } + }); + } + + + @ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent; + + public onContextMenu($event: MouseEvent, item: any): void { + this._contextMenuService.show.next({ + contextMenu: this.contextMenu, + event: $event, + item: item, + }); + $event.preventDefault(); + $event.stopPropagation(); + } + + private setValuesFromStore() { + const serviceInstance = this.store.getState().service.serviceInstance[this.serviceModelId]; + this.numServicesToDeploy = serviceInstance.bulkSize; + this.serviceName = serviceInstance.instanceName || '<Automatically Assigned>'; + + } + + public editService(): void { + this._iframeService.addClassOpenModal(this.parentElementClassName); + this.dialogService.addDialog(ServicePopupComponent, {}) + + } + + + extractOwningEntityNameAccordingtoId(id:String): string { + let owningEntityName; + _.forEach(this.store.getState().service.categoryParameters.owningEntityList,function(owningEntity: OwningEntity) { + if (owningEntity.id === id) { + owningEntityName = owningEntity.name; + + }}) + + return owningEntityName; + } + + extractServiceFields(): any { + let instanceFields : ServiceInstance; + instanceFields = this.store.getState().service.serviceInstance[Object.keys(this.store.getState().service.serviceInstance)[0]]; + instanceFields.subscriberName = this.store.getState().service.subscribers.find(sub => sub.id === instanceFields.globalSubscriberId).name; + instanceFields.owningEntityName = this.extractOwningEntityNameAccordingtoId(instanceFields.owningEntityId); + return instanceFields; + } + + public deployMacroservice(): void { + var instanceFields = this.extractServiceFields(); + instanceFields.rollbackOnFailure = instanceFields.rollbackOnFailure === 'true'; + this.msoService.submitMsoTask(instanceFields).subscribe((result) => { + window.parent.postMessage("navigateToInstantiationStatus", '*'); + }) + } + + closePage() { + let messageBoxData : MessageBoxData = new MessageBoxData( + "Delete Instantiation", // modal title + "You are about to stop the instantiation process of this service. \nAll data will be lost. Are you sure you want to stop?", + + ModalType.alert, + ModalSize.medium, + [ + {text:"Stop Instantiation", size:"large", callback: this.navigate.bind(this), closeModal:true}, + {text:"Cancel", size:"medium", closeModal:true} + ]); + + MessageBoxService.openModal.next(messageBoxData); + } + + navigate(){ + window.parent.postMessage("navigateTo", "*"); + } +} diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-header/tmp_instansiate_request.ts b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/tmp_instansiate_request.ts new file mode 100644 index 000000000..7accc3a9c --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-header/tmp_instansiate_request.ts @@ -0,0 +1,52 @@ +export default + { + "modelInfo": { + "modelType": "service", + "modelInvariantId": "5d48acb5-097d-4982-aeb2-f4a3bd87d31b", + "modelVersionId": "3c40d244-808e-42ca-b09a-256d83d19d0a", + "modelName": "MOW AVPN vMX BV vPE 1 Service", + "modelVersion": "10.0" + }, + "owningEntityId": "038d99af-0427-42c2-9d15-971b99b9b489", + "owningEntityName": "PACKET CORE", + "projectName": "{some project name}", + "globalSubscriberId": "{some subscriber id}", + "productFamilyId": "a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", + "instanceName": "vPE_Service", + "subscriptionServiceType": "VMX", + "lcpCloudRegionId": "mdt1", + "tenantId": "88a6ca3ee0394ade9403f075db23167e", + "vnfs": [ + { + "modelInfo": { + "modelName": "2016-73_MOW-AVPN-vPE-BV-L", + "modelVersionId": "7f40c192-f63c-463e-ba94-286933b895f8", + "modelCustomizationName": "2016-73_MOW-AVPN-vPE-BV-L 0", + "modelCustomizationId": "ab153b6e-c364-44c0-bef6-1f2982117f04" + }, + "lcpCloudRegionId": "mdt1", + "tenantId": "88a6ca3ee0394ade9403f075db23167e", + "platformName": "test", + "productFamilyId": "a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", + "instanceName": "vmxnjr001", + "instanceParams": [], + "vfModules": [ + { + "modelInfo": { + "modelType": "vfModule", + "modelName": "201673MowAvpnVpeBvL..AVPN_base_vPE_BV..module-0", + "modelVersionId": "4c75f813-fa91-45a4-89d0-790ff5f1ae79", + "modelCustomizationId": "a25e8e8c-58b8-4eec-810c-97dcc1f5cb7f" + }, + "instanceName": "vmxnjr001_AVPN_base_vPE_BV_base_001", + "instanceParams": [ + { + "vmx_int_net_len": "24" + } + ] + } + ] + } + ] + } + diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.component.ts b/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.component.ts new file mode 100644 index 000000000..6b717a930 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.component.ts @@ -0,0 +1,133 @@ +import {AfterViewInit, Component, EventEmitter, OnInit, Output, ViewChild} from '@angular/core'; +import { ContextMenuService } from 'ngx-contextmenu'; +import { Constants } from '../../shared/utils/constants'; +import {ServicePlanningService} from "../../services/service-planning.service"; +import {ITreeNode} from "angular-tree-component/dist/defs/api"; +import {ITreeOptions, TreeComponent} from "angular-tree-component"; +import {VnfPopupComponent} from "../../components/vnf-popup/vnf-popup.components"; +import {DialogService} from "ng2-bootstrap-modal"; +import {ActivatedRoute} from "@angular/router"; +import {NgRedux} from "@angular-redux/store"; +import {AppState} from "../../store/reducers"; +import { MessageBoxData, ModalSize, ModalType } from '../../shared/components/messageBox/messageBox.data'; +import { MessageBoxService } from '../../shared/components/messageBox/messageBox.service'; +import { deleteVnfInstance, deleteVfModuleInstance } from '../../service.actions'; +import { isNullOrUndefined } from 'util'; +import {IframeService} from "../../shared/utils/iframe.service"; + + +@Component({ + selector: 'drawing-board-tree', + templateUrl: './drawing-board-tree.html', + styleUrls : ['./drawing-board-tree.scss'] +}) + + +export class DrawingBoardTreeComponent implements OnInit, AfterViewInit { + constructor(private _contextMenuService: ContextMenuService, + private _servicePlanningService: ServicePlanningService, + private _iframeService : IframeService, + private dialogService: DialogService, + private store: NgRedux<AppState>, + private route: ActivatedRoute) { + this.route + .queryParams + .subscribe(params => { + this.serviceModelId = params['serviceModelId']; + }); + } + + @Output() + highlightNode : EventEmitter<number> = new EventEmitter<number>(); + + @ViewChild('tree') tree: TreeComponent; + missingDataTooltip: string = Constants.Error.MISSING_VNF_DETAILS; + currentNode: ITreeNode = null; // + nodes = []; + serviceModelId: string; + options: ITreeOptions = { + nodeHeight: 45, + dropSlotHeight: 1 + }; + parentElementClassName = 'content'; + + ngOnInit(): void { + this.store.subscribe(() => {this.updateTree()}); + this.updateTree() + } + + updateTree() { + const serviceInstance = this.store.getState().service.serviceInstance[this.serviceModelId]; + this.nodes = this._servicePlanningService.convertServiceInstanceToTreeData(serviceInstance, this.serviceModelId); + } + + ngAfterViewInit():void { + // Expand drawing tree on init. + this.tree.treeModel.expandAll(); + } + + public onContextMenu($event: MouseEvent, node: ITreeNode): void { + this.currentNode = node; + node.focus(); + node.setActiveAndVisible(false); + this.selectNode(node); + this._contextMenuService.show.next({ + event: <any>$event, + item: node, + }); + $event.preventDefault(); + $event.stopPropagation(); + } + + public editItem(node: ITreeNode): void { + node = this.currentNode; + this._iframeService.addClassOpenModal(this.parentElementClassName); + this.dialogService.addDialog(VnfPopupComponent, { + serviceModelId: this.serviceModelId, + modelName: node.data.modelName, + modelType: node.data.type, + parentModelName: node.parent.data.modelName, + isNewVfModule : false + }) + } + + public deleteItem(node: ITreeNode): void { + if(this.currentNode.data.type === 'VF'){ + if(!isNullOrUndefined(this.currentNode.data.children) && this.currentNode.data.children.length === 0){ + this.store.dispatch(deleteVnfInstance(this.currentNode.data.modelName, this.serviceModelId)); + }else { + let messageBoxData : MessageBoxData = new MessageBoxData( + "Remove VNF", // modal title + "You are about to remove this VNF and all its children from this service. Are you sure you want to remove it?", + + ModalType.alert, + ModalSize.medium, + [ + {text:"Remove VNF", size:"large", callback: this.removeVnf.bind(this), closeModal:true}, + {text:"Don’t Remove", size:"medium", closeModal:true} + ]); + + MessageBoxService.openModal.next(messageBoxData); + } + }else { + this.store.dispatch(deleteVfModuleInstance(this.currentNode.data.modelName, this.serviceModelId, node.parent.data.modelName)); + } + } + + removeVnf() { + this.store.dispatch(deleteVnfInstance(this.currentNode.data.modelName, this.serviceModelId)); + } + + public selectNode(node: ITreeNode): void { + node.expand(); + this.highlightNode.emit(node.data.modelId); + } + + isDataMissing(node: ITreeNode) { + //todo: currently not showing the alert icon. will be implemented in upcoming story. + return false; + } + +} + + diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.html b/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.html new file mode 100644 index 000000000..c4061db9e --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.html @@ -0,0 +1,42 @@ +<div class="drawing-board-tree"> + <div *ngIf="nodes?.length == 0" style="text-align: center; margin-top: 50px;"> + <no-content-message-and-icon class="span-over" + data-title="Please add objects (VNFs, network, modules etc.) from the left tree to design the service instance" + subtitle="Once done, click Deploy to start instantiation" + iconPath="./assets/img/UPLOAD.svg" + iconClass="upload-icon-service-planing"></no-content-message-and-icon> + </div> + <tree-root [attr.data-tests-id]="'drawing-board-tree'" #tree [nodes]="nodes" [options]="options" id="drawing-board-tree"> + <ng-template #treeNodeTemplate let-node let-index="index"> + <div [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName" (click)="selectNode(node)"> + <div class="model-info"> + <span> + <span class="property-name">{{node.data.type}}{{node.data.name ? ': ': ''}}<span class="auto-name">{{node.data.name? node.data.name: ''}}</span></span> + </span> + </div> + <div class="model-actions"> + <span class="icon-browse" [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName+'-menu-btn'" (click)="onContextMenu($event, node)" > + <context-menu> + <ng-template contextMenuItem (execute)="editItem(node)"> + <div [attr.data-tests-id]="'context-menu-item'"> + <span class="icon-edit"></span> + Edit + </div> + </ng-template> + <ng-template contextMenuItem (execute)="deleteItem(node)"> + <div> + <span class="icon-trash"></span> + Remove + </div> + </ng-template> + </context-menu> + </span> + <span *ngIf="isDataMissing(node)" class="icon-alert" tooltip="{{ missingDataTooltip }}" tooltipPlacement="left" [attr.data-tests-id]="'node-'+node.data.modelId +'-' +node.data.modelName+'-alert-icon'"></span> + </div> + </div> + + </ng-template> + </tree-root> +</div> + + diff --git a/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.scss b/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.scss new file mode 100644 index 000000000..fed9ead10 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.scss @@ -0,0 +1,274 @@ +@mixin highlight($background-color, $color) { + background: none; + padding: 0; + background-color: $background-color; + border: $color 1px solid; + color: $color; +} +@mixin highlight-toggle-children { + tree-node-expander { + .toggle-children-wrapper { + span.toggle-children { + @include highlight(white, #009FDB); + border-right: none; + } + } + } +} + +@mixin highlight-tree-node-content { + tree-node-content { + > div { + .model-actions { + .icon-browse:before { + display: inline-block; + } + } + } + } +} + +drawing-board-tree { + + .toggle-children-wrapper.toggle-children-wrapper-expanded { + .toggle-children:before { + color: #009FDB; + } + } + + overflow: auto; + flex: 1; + color: #5A5A5A; + line-height: 14px; + .drawing-board-tree { + width: 100%; + } + tree-viewport { + padding: 50px 3.5% 1% 6%; + tree-node { + tree-node-drop-slot { + .node-drop-slot { + display: none; + } + } + & .tree-node-focused, + & .tree-node-active { + & > tree-node-wrapper { + .node-wrapper { + @include highlight-toggle-children; + .node-content-wrapper-focused, + .node-content-wrapper-active + { + @include highlight(#E6F6FB, #009FDB); + .property-name { + color: #009FDB; + } + .auto-name { + font-family: OpenSans-Regular !important; + } + .icon-browse:before { + color: #5A5A5A; + } + @include highlight-tree-node-content; + } + } + } + } + & .tree-node-expanded { + > tree-node-wrapper .node-wrapper { + box-shadow: 0 0px 2px rgba(90,90,90,0.24); + } + } + + .tree-node-active .tree-children { + border: 1px solid #009FDB; + padding: 20px; + } + + + + .tree-node.tree-node-active.tree-node-expanded { + border: 1px solid #009FDB; + } + + .tree-node-leaf .node-wrapper{ + margin-left: -45px; + } + + tree-node-wrapper { + .node-wrapper { + height: 45px; + &:hover { + @include highlight-toggle-children; + .node-content-wrapper { + @include highlight(#E6F6FB, #009FDB); + .property-name { + color: #009FDB; + } + .icon-browse:before { + color: #5A5A5A; + } + @include highlight-tree-node-content; + } + } + tree-node-expander { + font-family: 'icomoon' !important; + height: 100%; + .toggle-children-wrapper { + padding: 0; + display: block; + height: 100%; + span.toggle-children { + display: flex; + width: 45px; + padding: 0; + top: 0; + height: inherit; + background-image: none; + background-color: white; + border: 1px solid #D2D2D2; + border-right: none; + &:before { + content: "\e900"; + font-size: 20px; + font-weight: 600; + text-align: center; + display: inline-block; + flex: auto; + align-self: center; + } + } + } + .toggle-children-wrapper-expanded { + span.toggle-children { + transform: none; + &:before { + content: "\e930"; + } + } + } + .toggle-children-placeholder { + width:45px; + } + } + .node-content-wrapper { + padding: 0; + background: none; + box-shadow: none; + border-radius: 0; + border: 1px solid #D2D2D2; + height: 100%; + flex: 1; + tree-node-content { + > div { + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; + .model-info { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 8px; + > span { + flex: 1; + padding: 0 8px; + span:nth-child(2) { + display: block; + } + } + } + .model-actions { + display: flex; + align-items: center; + padding-right: 12px; + .icon-browse { + padding: 0; + width: 30px; + &:before { + content: "\e924"; + font-size: 24px; + display: none; + } + &:hover:before { + color: #009FDB; + } + &:focus:before, + &:active:before { + color: #009FDB; + } + } + + .icon-alert { + padding-left: 10px; + &:before { + content: "\e904"; + font-size: 16px; + color: #ffb81c; + } + } + } + } + } + .property-name { + font-family: OpenSans-Semibold; + font-size: 12px; + line-height: 12px; + color: #191919; + text-transform: capitalize; + } + } + + + } + } + tree-node-children { + .tree-children { + padding: 20px; + .model-info span:first-child { + flex: 1.1 !important; + } + } + } + } + } +} +.cdk-overlay-pane.ngx-contextmenu { + ul.dropdown-menu { + width: 200px; + box-shadow: none; + padding: 0; + padding-top: 10px; + margin: 0; + border: 1px solid #D2D2D2; + border-top: 3px solid #009FDB; + box-shadow: 0 0px 2px rgba(90,90,90,0.24); + border-radius: 2px; + li { + height: 40px; + a { + font-family: OpenSans-Regular; + display: flex; + align-items: center; + height: 100%; + padding: 12px; + color: #5A5A5A; + &:hover { + background: #E6F6FB; + color: #009FDB; + } + span { + padding-right: 12px; + &.icon-edit:before { + content: '\e917'; + } + &.icon-trash:before { + content: '\e937'; + } + } + } + } + } +} + diff --git a/vid-webpack-master/src/app/drawingBoard/drawingBoard.module.ts b/vid-webpack-master/src/app/drawingBoard/drawingBoard.module.ts new file mode 100644 index 000000000..41efbe0e8 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/drawingBoard.module.ts @@ -0,0 +1,41 @@ +import { AvailableModelsTreeService } from './available-models-tree/available-models-tree.service'; +import { NgModule } from '@angular/core'; +import { HighlightPipe } from '../shared/pipes/highlight-filter.pipe'; +import { TreeModule } from 'angular-tree-component'; +import { BrowserModule } from '@angular/platform-browser'; +import { TooltipModule } from 'ngx-tooltip'; +import { AvailableModelsTreeComponent } from './available-models-tree/available-models-tree.component'; +import { ServicePlanningService } from '../services/service-planning.service'; +import { AaiService } from '../services/aaiService/aai.service'; +import { DrawingBoardTreeComponent } from './drawing-board-tree/drawing-board-tree.component'; +import { SharedModule } from '../shared/shared.module'; +import { ContextMenuModule, ContextMenuService } from 'ngx-contextmenu'; +import { CommonModule } from '@angular/common'; +import { DrawingBoardHeader } from './drawing-board-header/drawing-board-header.component'; +import { ServicePlanningComponent, ServicePlanningEmptyComponent } from './service-planning/service-planning.component'; + +@NgModule({ + imports: [ + TreeModule, + BrowserModule, + ContextMenuModule, + TooltipModule, + CommonModule, + SharedModule.forRoot()], + providers: [ + ServicePlanningService, + AaiService, + AvailableModelsTreeService , + ContextMenuService, + ServicePlanningService], + declarations: [ + AvailableModelsTreeComponent, + HighlightPipe, + DrawingBoardTreeComponent, + DrawingBoardHeader, + ServicePlanningComponent, + ServicePlanningEmptyComponent], + exports: [ AvailableModelsTreeComponent, DrawingBoardTreeComponent, DrawingBoardHeader] +}) + +export class DrawingBoardModule { } diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.html b/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.html new file mode 100644 index 000000000..5b2f22d5f --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.html @@ -0,0 +1,13 @@ +<div class="service-planning-header"> + <drawing-board-header></drawing-board-header> +</div> +<div class="service-planning-content row"> + <available-models-tree class="left-side col-md-6" (highlightInstances)="highlightInstancesBySelectingNode($event)"></available-models-tree> + <!--<no-content-message-and-icon *ngIf="!isShowTree()" class="span-over"--> + <!--data-title="Please add objects (VNFs, network, modules etc.) from the left tree to design the service instance"--> + <!--subtitle="Once done, click Deploy to start instantiation"--> + <!--iconPath="./img/UPLOAD.svg"--> + <!--iconClass="upload-icon-service-planing"></no-content-message-and-icon>--> + <drawing-board-tree *ngIf="isShowTree()" class="span-over col-md-6" (highlightNode)="highlightNodeBySelectingInstance($event)"></drawing-board-tree> +</div> + diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.scss b/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.scss new file mode 100644 index 000000000..69546a6c0 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.scss @@ -0,0 +1,16 @@ + +.service-planning-content { + display: flex; + flex: 1; +} + +.span-over { + display: flex; + flex: 1; +} + +//css for the icon. :host ::ng-deep are needed for applying css to child component +:host ::ng-deep .upload-icon-service-planing { + height: 117px; + margin-top: 32px; +} diff --git a/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.ts b/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.ts new file mode 100644 index 000000000..1ce0e8100 --- /dev/null +++ b/vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.ts @@ -0,0 +1,77 @@ +import {Component, ViewChild} from '@angular/core'; +import {DrawingBoardTreeComponent} from "../drawing-board-tree/drawing-board-tree.component"; +import {AvailableModelsTreeComponent} from "../available-models-tree/available-models-tree.component"; +import {ITreeNode} from "angular-tree-component/dist/defs/api"; +import {TreeComponent} from 'angular-tree-component'; + +@Component({ + selector: 'service-planning', + templateUrl: './service-planning.component.html', + styleUrls: ['./service-planning.component.scss'] +}) + +export class ServicePlanningComponent { + + @ViewChild(DrawingBoardTreeComponent) drawingModelTree; + @ViewChild(AvailableModelsTreeComponent) availableModelTree; + + isShowTree(): boolean { + return true; + } + + public highlightNodeBySelectingInstance(modelId: number): void { + this.availableModelTree.tree.treeModel.getNodeBy((node: ITreeNode) => node.data.id === modelId) + .setActiveAndVisible().expand(); + } + + public highlightInstancesBySelectingNode(id: number): void { + if(this.isShowTree()) { + let _this = this; + let matchInstances = _this.searchTree(id); + if (!matchInstances.length) + _this.clearSelectionInTree(_this.drawingModelTree.tree); + matchInstances.forEach(function (instance, index) { + let multi : boolean = !!index; + _this.drawingModelTree.tree.treeModel.getNodeById(instance.id) + .setActiveAndVisible(multi).expand(); + }); + + } + } + + clearSelectionInTree(tree: TreeComponent): void { + let activateNode = tree.treeModel.getActiveNode(); + activateNode ? activateNode.toggleActivated().blur() : null; + } + + searchTree(modelId: number) { + let _this = this; + let results = []; + let nodes = _this.drawingModelTree.nodes; + nodes.forEach(function (node) { + _this.searchTreeNode(node, modelId, results); + }); + return results; + } + + searchTreeNode(node, modelId: number, results): void { + if(node.modelId === modelId){ + results.push(node); + } + if (node.children != null){ + for(let i = 0; i < node.children.length; i++){ + this.searchTreeNode(node.children[i], modelId, results); + } + } + } + + +} + +export class ServicePlanningEmptyComponent extends ServicePlanningComponent { + isShowTree() : boolean { + return false; + } +} + + |