summaryrefslogtreecommitdiffstats
path: root/vid-webpack-master/src/app/drawingBoard
diff options
context:
space:
mode:
authorSonsino, Ofir (os0695) <os0695@intl.att.com>2018-07-10 15:57:37 +0300
committerSonsino, Ofir (os0695) <os0695@intl.att.com>2018-07-10 15:57:37 +0300
commitff76b5ed0aa91d5fdf9dc4f95e8b20f91ed9d072 (patch)
treeaae42404a93fdffdd16ff050eaa28129959f7577 /vid-webpack-master/src/app/drawingBoard
parentc72d565bb58226b20625b2bce5f0019046bee649 (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/drawingBoard')
-rw-r--r--vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.html35
-rw-r--r--vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.scss398
-rw-r--r--vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.component.ts166
-rw-r--r--vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.spec.ts450
-rw-r--r--vid-webpack-master/src/app/drawingBoard/available-models-tree/available-models-tree.service.ts36
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.html33
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.scss95
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-header/drawing-board-header.component.ts119
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-header/tmp_instansiate_request.ts52
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.component.ts133
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.html42
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawing-board-tree/drawing-board-tree.scss274
-rw-r--r--vid-webpack-master/src/app/drawingBoard/drawingBoard.module.ts41
-rw-r--r--vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.html13
-rw-r--r--vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.scss16
-rw-r--r--vid-webpack-master/src/app/drawingBoard/service-planning/service-planning.component.ts77
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;
+ }
+}
+
+