blob: 687f4fa9eb3137344a255d9aeb4d029fe2345a0d [file] [log] [blame]
/*
Copyright 2020 TATA ELXSI
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
*/
/**
* @file NS Compose Component
*/
// tslint:disable: no-increment-decrement
import { HttpHeaders } from '@angular/common/http';
import { Component, ElementRef, Injector, ViewChild, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { TranslateService } from '@ngx-translate/core';
import { NotifierService } from 'angular-notifier';
import { APIURLHEADER, CONSTANTNUMBER, ERRORDATA, MODALCLOSERESPONSEDATA, MODALCLOSERESPONSEWITHCP } from 'CommonModel';
import { ConfirmationTopologyComponent } from 'ConfirmationTopology';
import * as d3 from 'd3';
import { DataService } from 'DataService';
import { environment } from 'environment';
import * as HttpStatus from 'http-status-codes';
import * as jsyaml from 'js-yaml';
import { COMPOSERNODES, CONSTITUENTVNFD, GRAPHDETAILS, NSDDetails, Tick, TickPath, VLD, VNFDCONNECTIONPOINTREF } from 'NSDModel';
import { RestService } from 'RestService';
import { SharedService } from 'SharedService';
import { isNullOrUndefined } from 'util';
import { VNFData, VNFDDetails } from 'VNFDModel';
/**
* Creating component
* @Component takes NSComposerComponent.html as template url
*/
@Component({
selector: 'app-ns-composer',
templateUrl: './NSComposerComponent.html',
styleUrls: ['./NSComposerComponent.scss'],
encapsulation: ViewEncapsulation.None
})
/** Exporting a class @exports NSComposerComponent */
export class NSComposerComponent {
/** To inject services @public */
public injector: Injector;
/** View child contains graphContainer ref @public */
@ViewChild('graphContainer', { static: true }) public graphContainer: ElementRef;
/** dataService to pass the data from one component to another @public */
public dataService: DataService;
/** Contains VNFD Informations @public */
public vnfdPackageDetails: VNFData = { identifier: '', shortName: '', vendor: '', description: '', version: '', id: '', name: '' };
/** Contains VL Details @public */
public vlDetails: VLD = {
name: '',
'mgmt-network': true,
'vim-network-name': '',
type: '',
id: ''
};
/** Contains the information of the type of modification @public */
public putType: string;
/** Conatins mousedown action @public */
public mousedownNode: COMPOSERNODES = null;
/** Conatins mouseup action @public */
public mouseupNode: COMPOSERNODES = null;
/** Conatins mousedownLink action @public */
public mousedownLink: COMPOSERNODES = null;
/** Conatins current Selection node action @public */
public currentSelectedNode: COMPOSERNODES = null;
/** Conatins current Selection node action @public */
public currentSelectedLink: COMPOSERNODES = null;
/** Need to show the NSD Details @public */
public isShowNSDDetails: boolean = true;
/** Contains the node information of VL @public */
public vlNodes: {}[] = [];
/** Need to show the VL Details @public */
public isShowVLDetails: boolean = false;
/** Contains the node information of VNF @public */
public vnfNodes: {}[] = [];
/** contains the VNF Details @public */
public vnfData: CONSTITUENTVNFD;
/** Need to show the VNF Details @public */
public isShowVNFDetails: boolean = false;
/** Contains the node information of CP @public */
public cpNodes: {}[] = [];
/** Need to show the CP Details */
public cpData: VNFDCONNECTIONPOINTREF;
/** Need to show the VNF Details @public */
public isShowCPDetails: boolean = false;
/** random number count @public */
public randomNumberLength: number;
/** Contains the vnfd information @public */
public vnfList: VNFDDetails[] = [];
/** Add the activeclass for the selected @public */
public activeClass: string = 'active';
/** Add the fixed class for the freeze @public */
public fixedClass: string = 'fixed';
/** Check the loading results @public */
public isLoadingResults: boolean = true;
/** Give the message for the loading @public */
public message: string = 'PLEASEWAIT';
/** Get VNF selected node @public */
public getVNFSelectedData: VNFDDetails[];
/** Assign the forcesimulation active @public */
public forceSimulationActive: boolean = false;
/** Assign pinned class for the button when freezed @public */
public classApplied: boolean = false;
/** Contains sidebar open status @public */
public sideBarOpened: boolean = false;
/** Contains SVG attributes @private */
// tslint:disable-next-line:no-any
private svg: any;
/** Contains the Drag line */
// tslint:disable-next-line: no-any
private dragLine: any;
/** Contains VL node @private */
// tslint:disable-next-line:no-any
private vlNode: any;
/** Contains VNFD node @private */
// tslint:disable-next-line:no-any
private vnfdnode: any;
/** Contains CP node @private */
// tslint:disable-next-line:no-any
private cpnode: any;
/** Rendered nodes represent VL @private */
// tslint:disable-next-line:no-any
private gvlNode: any;
/** Rendered nodes represent VL @private */
// tslint:disable-next-line:no-any
private gvnfdNode: any;
/** Rendered nodes represent VL @private */
// tslint:disable-next-line:no-any
private gcpNode: any;
/** Contains forced node animations @private */
// tslint:disable-next-line:no-any
private force: any;
/** Contains all the selected node @private */
private selectedNode: COMPOSERNODES[] = [];
/** variables used for CP @private */
private iConnectionPointRef: number = 0;
/** Contains the connected point @private */
private connectionPoint: string;
/** Contains all the NSD information @private */
private nsd: string;
/** Contains all the VNFD information @private */
private vnfd: string;
/** Contains id of the node @private */
private identifier: string;
/** Variables used for cp @private */
private jConnectionPointRef: number = 0;
/** Contains copy of NSD information @private */
private nsdCopy: string;
/** Contains the VNFD copy @private */
private vnfdCopy: string;
/** Contains name of the node @private */
private name: string;
/** Contains member vnf index value of the node @private */
private memberVnfIndexValue: number = 0;
/** Contains path information of the node */
// tslint:disable-next-line:no-any
private path: any;
/** Contains the node information @private */
private nodes: COMPOSERNODES[] = [];
/** Contains the link information of nodes @private */
private links: {}[] = [];
/** Contains the NS information @private */
private nsData: NSDDetails;
/** Instance of the rest service @private */
private restService: RestService;
/** Service holds the router information @private */
private router: Router;
/** Holds teh instance of AuthService class of type AuthService @private */
private activatedRoute: ActivatedRoute;
/** Notifier service to popup notification @private */
private notifierService: NotifierService;
/** Controls the header form @private */
private headers: HttpHeaders;
/** Contains tranlsate instance @private */
private translateService: TranslateService;
/** Contains lastkeypressed instance @private */
private lastKeyDown: number = -1;
/** Instance of the modal service @private */
private modalService: NgbModal;
/** Setting the Value of connection point refrence of the CP @private */
private setVnfdConnectionPointRef: string;
/** Setting the Value of VL name for confirmation @private */
private vlName: string;
/** Setting the Value of VNFD name for confirmation @private */
private setVnfdName: string;
/** Contains all methods related to shared @private */
private sharedService: SharedService;
/** Contains selected node VLD objects @private */
private selectedVLDResult: VLD;
constructor(injector: Injector) {
this.injector = injector;
this.restService = this.injector.get(RestService);
this.dataService = this.injector.get(DataService);
this.router = this.injector.get(Router);
this.activatedRoute = this.injector.get(ActivatedRoute);
this.notifierService = this.injector.get(NotifierService);
this.translateService = this.injector.get(TranslateService);
this.modalService = this.injector.get(NgbModal);
this.sharedService = this.injector.get(SharedService);
}
/** Lifecyle Hooks the trigger before component is instantiate @public */
public ngOnInit(): void {
// tslint:disable-next-line:no-backbone-get-set-outside-model
this.identifier = this.activatedRoute.snapshot.paramMap.get('id');
this.generateData();
this.headers = new HttpHeaders({
'Content-Type': 'application/zip',
Accept: 'application/json',
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
});
}
/** Events handles at drag on D3 region @public */
// tslint:disable-next-line:no-any
public drag(ev: any): void {
if (ev.target.id === 'vl') {
ev.dataTransfer.setData('text', ev.target.id);
} else {
ev.dataTransfer.setData('text', ev.target.attributes['data-id'].value);
}
}
/** On clicking redirect to NS edit page @public */
public onEdit(): void {
this.router.navigate(['/packages/ns/edit/', this.identifier]).catch(() => {
// Catch Navigation Error
});
}
/** Events handles drop at D3 region @public */
public drop(ev: DragEvent): void {
event.preventDefault();
this.name = ev.dataTransfer.getData('text');
if (this.name === 'vl') {
this.svg.selectAll('*').remove();
this.vldropComposer();
} else {
this.svg.selectAll('*').remove();
this.vnfd = ev.dataTransfer.getData('text');
this.vnfdropComposer();
}
}
/** Drop VL Composer Data @public */
public vldropComposer(): void {
this.randomNumberLength = CONSTANTNUMBER.randomNumber;
const generateId: string = 'ns_vl_' + this.randomString(
this.randomNumberLength,
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
);
if (this.nsData.vld !== undefined) {
this.nsData.vld.push({
'vim-network-name': 'PUBLIC',
name: generateId,
'mgmt-network': true,
type: 'ELAN',
id: generateId
});
} else {
Object.assign(this.nsData, {
vld: [{
'vim-network-name': 'PUBLIC',
name: generateId,
'mgmt-network': true,
type: 'ELAN',
id: generateId
}]
});
}
this.putType = 'nsdadd';
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, this.nsData, this.putType);
}
/** Drop VNFD Composer Data @public */
public vnfdropComposer(): void {
if (this.nsData['constituent-vnfd'] !== undefined) {
this.nsData['constituent-vnfd'].push({
'vnfd-id-ref': this.vnfd,
'member-vnf-index': ++this.memberVnfIndexValue
});
} else {
Object.assign(this.nsData, {
'constituent-vnfd': [{
'vnfd-id-ref': this.vnfd,
'member-vnf-index': ++this.memberVnfIndexValue
}]
});
}
this.putType = 'vnfdadd';
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, this.nsData, this.putType);
}
/** Events handles allow drop on D3 region @public */
public allowDrop(ev: DragEvent): void {
ev.preventDefault();
}
/** Save NSD Information @public */
public saveNSD(): void {
if (this.vnfdPackageDetails.shortName !== undefined) {
this.nsData['short-name'] = this.vnfdPackageDetails.shortName;
}
if (this.vnfdPackageDetails.vendor !== undefined) {
this.nsData.vendor = this.vnfdPackageDetails.vendor;
}
if (this.vnfdPackageDetails.description !== undefined) {
this.nsData.description = this.vnfdPackageDetails.description;
}
if (this.vnfdPackageDetails.version !== undefined) {
this.nsData.version = this.vnfdPackageDetails.version;
}
if (this.vnfdPackageDetails.id !== undefined) {
this.nsData.id = this.vnfdPackageDetails.id;
}
if (this.vnfdPackageDetails.name !== undefined) {
this.nsData.name = this.vnfdPackageDetails.name;
}
this.putType = 'nsdUpdate';
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, this.nsData, this.putType);
}
/** Save Virtual Link @public */
public saveVL(vlid: string): void {
this.nsData.vld.forEach((result: VLD) => {
if (result.id === vlid) {
result.name = this.vlDetails.name;
result['mgmt-network'] = !isNullOrUndefined(this.vlDetails['mgmt-network']) ? this.vlDetails['mgmt-network'] : true;
result['vim-network-name'] = !isNullOrUndefined(this.vlDetails['vim-network-name']) ? this.vlDetails['vim-network-name'] : '';
result.type = this.vlDetails.type;
result.id = this.vlDetails.id;
}
});
this.putType = 'vlUpdate';
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, this.nsData, this.putType);
}
/** Add the new Data @public */
public addData(apiURL: string, identifier: string, data: NSDDetails, putType: string): void {
this.isLoadingResults = true;
let successMessage: string = '';
if (putType === 'nsdadd') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.ADDNSD';
} else if (putType === 'vnfdadd') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.ADDVNFD';
} else if (putType === 'cpAdded') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.ADDNS';
} else if (putType === 'nsdUpdate') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.UPDATEDSUCCESSFULLY';
} else if (putType === 'vlUpdate') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.UPDATEDSUCCESSFULLY';
} else if (putType === 'nsddelete') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.DELETENSD';
} else if (putType === 'vnfddelete') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.DELETEVNFD';
} else if (putType === 'nsdelete') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.DELETENS';
} else if (putType === 'linkdelete') {
successMessage = 'PAGE.NSPACKAGE.NSCOMPOSE.DELETELINK';
}
/** Below hide for conflicts with light weight UI */
const apiURLHeader: APIURLHEADER = {
url: apiURL + '/' + identifier + '/nsd_content',
httpOptions: { headers: this.headers }
};
const nsData: {} = {};
nsData['nsd:nsd-catalog'] = {};
nsData['nsd:nsd-catalog'].nsd = [];
nsData['nsd:nsd-catalog'].nsd.push(data);
const descriptorInfo: string = jsyaml.dump(nsData, {sortKeys: true});
this.sharedService.targzFile({ packageType: 'nsd', id: this.identifier, descriptor: descriptorInfo })
.then((content: ArrayBuffer): void => {
this.restService.putResource(apiURLHeader, content).subscribe((res: {}) => {
this.generateData();
this.notifierService.notify('success', this.translateService.instant(successMessage));
this.isLoadingResults = false;
}, (error: ERRORDATA) => {
this.generateData();
this.restService.handleError(error, 'put');
this.isLoadingResults = false;
});
}).catch((): void => {
this.notifierService.notify('error', this.translateService.instant('ERROR'));
this.isLoadingResults = false;
});
}
/** Show Info @public */
public showInfo(): void {
const modalRef: NgbModalRef = this.modalService.open(ConfirmationTopologyComponent, { backdrop: 'static' });
modalRef.componentInstance.topologyType = 'Info';
modalRef.componentInstance.topologytitle = this.translateService.instant('PAGE.TOPOLOGY.INFO');
modalRef.result.then((result: MODALCLOSERESPONSEDATA) => {
if (result) {
// empty
}
}).catch();
}
/** Event to freeze the animation @public */
public onFreeze(): void {
this.classApplied = !this.classApplied;
const alreadyFixedIsActive: boolean = d3.select('svg#graphContainer').classed(this.fixedClass);
d3.select('svg#graphContainer').classed(this.fixedClass, !alreadyFixedIsActive);
if (alreadyFixedIsActive) {
this.force.stop();
}
this.forceSimulationActive = alreadyFixedIsActive;
this.nodes.forEach((d: COMPOSERNODES) => {
d.fx = (alreadyFixedIsActive) ? null : d.x;
d.fy = (alreadyFixedIsActive) ? null : d.y;
});
if (alreadyFixedIsActive) {
this.force.restart();
}
}
/** Events handles when dragended @public */
public toggleSidebar(): void {
this.sideBarOpened = !this.sideBarOpened;
this.deselectAllNodes();
this.showRightSideInfo(true, false, false, false);
}
/** Prepare information for node creation of VNFD @private */
private generateData(): void {
this.generateVNFData();
this.generateDataNSDTopology();
this.sideBarOpened = false;
}
/** Prepare the information of the VNFD @private */
private generateVNFData(): void {
this.restService.getResource(environment.VNFPACKAGESCONTENT_URL).subscribe((vnfdPackageData: VNFDDetails[]) => {
this.vnfList = vnfdPackageData;
}, (error: ERRORDATA) => {
this.restService.handleError(error, 'get');
});
}
/** Prepare information for node creation of NSD Topology @private */
private generateDataNSDTopology(): void {
this.nodes = [];
this.links = [];
this.iConnectionPointRef = 0;
this.jConnectionPointRef = 0;
this.restService.getResource(environment.NSDESCRIPTORSCONTENT_URL + '/' + this.identifier).subscribe((nsData: NSDDetails) => {
delete nsData._admin;
delete nsData._id;
delete nsData._links;
this.nsData = nsData;
this.vnfdPackageDetails.shortName = nsData['short-name'];
this.vnfdPackageDetails.vendor = nsData.vendor;
this.vnfdPackageDetails.description = nsData.description;
this.vnfdPackageDetails.version = nsData.version;
this.vnfdPackageDetails.id = nsData.id;
this.vnfdPackageDetails.name = nsData.name;
if (nsData.vld !== undefined) {
/** Details of the VL */
this.nsDataVLD(nsData);
}
if (nsData['constituent-vnfd'] !== undefined) {
/** Details of the VNFD */
this.nsDataConstituentVNFD(nsData);
}
if (nsData.vld !== undefined) {
this.nsDataVLDLinkCreation(nsData);
}
this.separateAndCreatenode();
}, (error: ERRORDATA) => {
if (error.error.status === HttpStatus.NOT_FOUND || error.error.status === HttpStatus.UNAUTHORIZED) {
this.router.navigateByUrl('404', { skipLocationChange: true }).catch();
} else {
this.restService.handleError(error, 'get');
}
this.isLoadingResults = false;
this.isShowNSDDetails = false;
});
}
/** nsData-vld undefined Call this function @private */
private nsDataVLD(nsData: NSDDetails): void {
nsData.vld.forEach((res: VLD) => {
this.nodes.push({ id: res.id, reflexive: false, type: 'vld', name: res.id, selectorId: res.id });
this.nsd = res.id;
if (res['vnfd-connection-point-ref'] !== undefined) {
res['vnfd-connection-point-ref'].forEach((result: VNFDCONNECTIONPOINTREF) => {
this.nodes.push(
{
id: this.nsd + ++this.iConnectionPointRef + ':' + result['vnfd-connection-point-ref'],
reflexive: false,
type: 'ns',
name: result['vnfd-connection-point-ref'],
nodeIndex: result['member-vnf-index-ref'],
selectorId: result['vnfd-connection-point-ref'] + '_' + result['member-vnf-index-ref'] + '-osm-' + this.nsd
});
});
}
});
}
/** nsData constituent-vnfd undefined Call this function @private */
private nsDataConstituentVNFD(nsData: NSDDetails): void {
nsData['constituent-vnfd'].forEach((res: CONSTITUENTVNFD) => {
this.nodes.push(
{
id: res['vnfd-id-ref'] + ':' + res['member-vnf-index'],
reflexive: false,
type: 'vnfd',
name: res['vnfd-id-ref'],
nodeIndex: res['member-vnf-index'],
selectorId: res['vnfd-id-ref'] + '_' + res['member-vnf-index']
});
this.vnfd = res['vnfd-id-ref'];
this.memberVnfIndexValue = res['member-vnf-index'];
});
}
/** nsData-vld undefined Call this function @private */
private nsDataVLDLinkCreation(nsData: NSDDetails): void {
nsData.vld.forEach((res: VLD) => {
this.nsdCopy = res.id;
if (res['vnfd-connection-point-ref'] !== undefined) {
this.nsDataVNFDConnectionPointRefrence(res);
}
});
}
/** nsData-vnfd-connection-point-ref undefined Call this function @private */
private nsDataVNFDConnectionPointRefrence(res: VLD): void {
res['vnfd-connection-point-ref'].forEach((result: VNFDCONNECTIONPOINTREF) => {
this.connectionPoint = this.nsdCopy + ++this.jConnectionPointRef + ':' + result['vnfd-connection-point-ref'];
this.vnfdCopy = result['vnfd-id-ref'] + ':' + result['member-vnf-index-ref'];
const connectionPointPos: number = this.nodes.map((e: COMPOSERNODES) => { return e.id; }).indexOf(this.connectionPoint);
const nsdPos: number = this.nodes.map((e: COMPOSERNODES) => { return e.id; }).indexOf(this.nsdCopy);
const vnfdPos: number = this.nodes.map((e: COMPOSERNODES) => { return e.id; }).indexOf(this.vnfdCopy);
this.links.push(
{
source: this.nodes[connectionPointPos],
target: this.nodes[nsdPos]
},
{
source: this.nodes[connectionPointPos],
target: this.nodes[vnfdPos]
});
});
}
/** Generate random string @private */
private randomString(length: number, chars: string): string {
let result: string = '';
for (let randomStringRef: number = length; randomStringRef > 0; --randomStringRef) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
/** Separate and create node @private */
private separateAndCreatenode(): void {
this.seprateNodes(this.nodes);
this.createnode(this.nodes);
this.isLoadingResults = false;
}
/** Get the default Configuration of containers @private */
private getGraphContainerAttr(): GRAPHDETAILS {
return {
width: 700,
height: 400,
nodeHeight: 50,
nodeWidth: 35,
textX: -35,
textY: 30,
radius: 5,
distance: 50,
strength: -500,
forcex: 2,
forcey: 2,
sourcePaddingYes: 17,
sourcePaddingNo: 12,
targetPaddingYes: 4,
targetPaddingNo: 3,
alphaTarget: 0.3,
imageX: -25,
imageY: -25,
shiftKeyCode: 17
};
}
/** Separate the nodes along with its tyep @private */
private seprateNodes(node: COMPOSERNODES[]): void {
this.vlNodes = []; this.vnfNodes = []; this.cpNodes = [];
node.forEach((nodeList: COMPOSERNODES) => {
if (nodeList.type === 'vld') {
this.vlNodes.push(nodeList);
} else if (nodeList.type === 'vnfd') {
this.vnfNodes.push(nodeList);
} else if (nodeList.type === 'ns') {
this.cpNodes.push(nodeList);
}
});
}
/** Node is created and render at D3 region @private */
private createnode(node: COMPOSERNODES[]): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
d3.selectAll('svg#graphContainer > *').remove();
d3.select(window).on('keydown', () => { this.keyDown(); });
d3.select(window).on('keyup', () => { this.keyUp(); });
this.svg = d3.select('#graphContainer')
.attr('oncontextmenu', 'return false;')
.attr('width', graphContainerAttr.width)
.attr('height', graphContainerAttr.height)
.on('mousemove', () => { this.mousemove(); });
this.force = d3.forceSimulation()
.force('charge', d3.forceManyBody().strength(graphContainerAttr.strength))
.force('link', d3.forceLink().id((d: TickPath) => d.id).distance(graphContainerAttr.distance))
.force('center', d3.forceCenter(graphContainerAttr.width / graphContainerAttr.forcex,
graphContainerAttr.height / graphContainerAttr.forcey))
.force('x', d3.forceX(graphContainerAttr.width / graphContainerAttr.forcex))
.force('y', d3.forceY(graphContainerAttr.height / graphContainerAttr.forcey))
.on('tick', () => { this.tick(); });
this.dragLine = this.svg.append('svg:path').attr('class', 'link dragline hidden').attr('d', 'M0,0L0,0');
this.path = this.svg.append('svg:g').selectAll('path');
this.vlNode = this.svg.append('svg:g').selectAll('vlnode');
this.vnfdnode = this.svg.append('svg:g').selectAll('vnfdnode');
this.cpnode = this.svg.append('svg:g').selectAll('cpnode');
// app starts here
this.restart(node);
}
/** update force layout (called automatically each iteration) @private */
private tick(): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
// draw directed edges with proper padding from node centers
this.path.attr('class', 'link').attr('d', (d: Tick) => {
const deltaX: number = d.target.x - d.source.x;
const deltaY: number = d.target.y - d.source.y;
const dist: number = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const normX: number = deltaX / dist;
const normY: number = deltaY / dist;
const sourcePadding: number = d.left ? graphContainerAttr.sourcePaddingYes : graphContainerAttr.sourcePaddingNo;
const targetPadding: number = d.right ? graphContainerAttr.targetPaddingYes : graphContainerAttr.targetPaddingNo;
const sourceX: number = d.source.x + (sourcePadding * normX);
const sourceY: number = d.source.y + (sourcePadding * normY);
const targetX: number = d.target.x - (targetPadding * normX);
const targetY: number = d.target.y - (targetPadding * normY);
return `M${sourceX},${sourceY}L${targetX},${targetY}`;
}).on('dblclick', (d: Tick) => { this.getDeleteLinkConfirmation(d); });
this.vlNode.attr('transform', (t: TickPath) => `translate(${t.x},${t.y})`);
this.vnfdnode.attr('transform', (t: TickPath) => `translate(${t.x},${t.y})`);
this.cpnode.attr('transform', (t: TickPath) => `translate(${t.x},${t.y})`);
}
/** Update graph (called when needed) at D3 region @private */
private restart(node: COMPOSERNODES[]): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
this.path = this.path.data(this.links);
this.vlNode = this.vlNode.data(this.vlNodes, (d: COMPOSERNODES) => d.id);
this.vnfdnode = this.vnfdnode.data(this.vnfNodes, (d: COMPOSERNODES) => d.id);
this.cpnode = this.cpnode.data(this.cpNodes, (d: COMPOSERNODES) => d.id);
this.resetAndCreateNodes();
this.force.nodes(node).force('link').links(this.links);
this.force.alphaTarget(graphContainerAttr.alphaTarget).restart();
}
/** Rest and create nodes @private */
private resetAndCreateNodes(): void {
this.path.exit().remove();
this.vlNode.exit().remove();
this.vnfdnode.exit().remove();
this.cpnode.exit().remove();
this.getPathNodes();
this.getVLNodes();
this.getVNFDNodes();
this.getCPNodes();
this.path.merge(this.path);
this.vlNode = this.gvlNode.merge(this.vlNode);
this.vnfdnode = this.gvnfdNode.merge(this.vnfdnode);
this.cpnode = this.gcpNode.merge(this.cpnode);
}
/** setting the Path @private */
private getPathNodes(): void {
this.path = this.path.enter().append('svg:path');
}
/** Setting all the VL nodes @private */
private getVLNodes(): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
this.gvlNode = this.vlNode.enter().append('svg:g');
this.gvlNode.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
this.gvlNode.append('svg:image')
.style('opacity', 1)
.attr('x', graphContainerAttr.imageX)
.attr('y', graphContainerAttr.imageY)
.attr('id', (d: COMPOSERNODES) => { return d.selectorId; })
.attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
.attr('xlink:href', 'assets/images/VL.svg')
.on('mousedown', (d: COMPOSERNODES) => { this.mouseDown(d); })
.on('mouseup', (d: COMPOSERNODES) => { this.mouseUp(d); })
.on('click', (d: COMPOSERNODES) => { this.singleClick(this.vlNode, d); this.onNodeClickToggleSidebar(); })
.on('dblclick', (d: COMPOSERNODES) => { this.getDeleteConfirmation(d); this.onNodedblClickToggleSidebar(); });
this.gvlNode.append('svg:text')
.attr('class', 'node_text')
.attr('y', graphContainerAttr.textY)
.text((d: COMPOSERNODES) => d.id);
}
/** Setting all the VNFD nodes @private */
private getVNFDNodes(): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
this.gvnfdNode = this.vnfdnode.enter().append('svg:g');
this.gvnfdNode.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
this.gvnfdNode.append('svg:image')
.style('opacity', 1)
.attr('x', graphContainerAttr.imageX)
.attr('y', graphContainerAttr.imageY)
.attr('id', (d: COMPOSERNODES) => { return d.selectorId; })
.attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
.attr('xlink:href', 'assets/images/VNFD.svg')
.on('mousedown', (d: COMPOSERNODES) => { this.mouseDown(d); })
.on('mouseup', (d: COMPOSERNODES) => { this.mouseUp(d); })
.on('click', (d: COMPOSERNODES) => { this.singleClick(this.vnfdnode, d); this.onNodeClickToggleSidebar(); })
.on('dblclick', (d: COMPOSERNODES) => { this.getDeleteConfirmation(d); this.onNodedblClickToggleSidebar(); });
this.gvnfdNode.append('svg:text')
.attr('class', 'node_text')
.attr('y', graphContainerAttr.textY)
.text((d: COMPOSERNODES) => d.id);
}
/** Setting all the CP nodes @private */
private getCPNodes(): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
this.gcpNode = this.cpnode.enter().append('svg:g');
this.gcpNode.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
this.gcpNode.append('svg:image')
.style('opacity', 1)
.attr('x', graphContainerAttr.imageX)
.attr('y', graphContainerAttr.imageY)
.attr('id', (d: COMPOSERNODES) => { return d.selectorId; })
.attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
.attr('xlink:href', 'assets/images/CP.svg')
.on('mousedown', (d: COMPOSERNODES) => { this.mouseDown(d); })
.on('mouseup', (d: COMPOSERNODES) => { this.mouseUp(d); })
.on('click', (d: COMPOSERNODES) => { this.singleClick(this.cpnode, d); this.onNodeClickToggleSidebar(); })
.on('dblclick', (d: COMPOSERNODES) => { this.getDeleteConfirmation(d); this.onNodedblClickToggleSidebar(); });
this.gcpNode.append('svg:text')
.attr('class', 'node_text')
.attr('y', graphContainerAttr.textY)
.text((d: COMPOSERNODES) => d.id);
}
/** Events handles when mousemove it will capture the selected node data @private */
private mousemove(): void {
if (!this.mousedownNode) { return; }
this.dragLine.attr('d',
`M${this.mousedownNode.x},${this.mousedownNode.y}L${d3.mouse(d3.event.currentTarget)[0]},${d3.mouse(d3.event.currentTarget)[1]}`);
}
/** Get confirmation Before Deleting the Link in Topology @private */
private getAddConfirmation(mouseData: COMPOSERNODES, getNsData: NSDDetails, addType: string, getVLDIndex: number): void {
let findVNFName: string = '';
let findVLDID: string = '';
if (mouseData.type === 'vld') {
findVNFName = this.mouseupNode.name;
findVLDID = this.mousedownNode.id;
} else {
findVNFName = this.mousedownNode.name;
findVLDID = this.mouseupNode.id;
}
getNsData.vld.forEach((result: VLD) => {
if (result.id === findVLDID) {
this.vlName = result.name;
this.getVNFSelectedData = this.vnfList.filter((vnfList: VNFDDetails) => vnfList.id === findVNFName);
this.setVnfdConnectionPointRef = this.getVNFSelectedData[0]['mgmt-interface'].cp;
this.setVnfdName = this.getVNFSelectedData[0].name;
this.selectedVLDResult = result;
}
});
if (this.vlName !== undefined && this.setVnfdName !== undefined && this.setVnfdConnectionPointRef !== undefined) {
const modalRef: NgbModalRef = this.modalService.open(ConfirmationTopologyComponent, { backdrop: 'static' });
modalRef.componentInstance.topologyType = 'Add';
modalRef.componentInstance.cpDetails = this.getVNFSelectedData[0]['connection-point'];
this.translateService.get('PAGE.TOPOLOGY.ADDINGCP', {
vlname: '<b>' + this.vlName + '</b>',
vnfdname: '<b>' + this.setVnfdName + '</b>',
cpname: '<b>' + this.setVnfdConnectionPointRef + '</b>'
}).subscribe((res: string) => {
modalRef.componentInstance.topologyname = res;
});
modalRef.componentInstance.topologytitle = this.translateService.instant('PAGE.TOPOLOGY.CONNECTIONPOINT');
modalRef.result.then((result: MODALCLOSERESPONSEWITHCP) => {
if (result) {
this.nsData = getNsData;
this.generateCPForVNF(this.selectedVLDResult, result.connection_point, getVLDIndex);
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, getNsData, addType);
} else {
this.deselectPath();
}
}).catch();
} else {
this.deselectPath();
this.notifierService.notify('error', this.translateService.instant('ERROR'));
}
}
/** Generate connection point for vnf using vld @private */
private generateCPForVNF(result: VLD, cp: string, getVLDIndex: number): void {
if (result['vnfd-connection-point-ref'] !== undefined) {
result['vnfd-connection-point-ref'].push({
'member-vnf-index-ref': getVLDIndex,
'vnfd-connection-point-ref': cp,
'vnfd-id-ref': this.getVNFSelectedData[0].name
});
} else {
Object.assign(result, {
'vnfd-connection-point-ref': [{
'member-vnf-index-ref': getVLDIndex,
'vnfd-connection-point-ref': cp,
'vnfd-id-ref': this.getVNFSelectedData[0].name
}]
});
}
}
/** Events handles when mousedown click it will capture the selected node data @private */
private mouseDown(d: COMPOSERNODES): void {
event.preventDefault();
if (d3.event.ctrlKey) { return; }
if (d3.event.shiftKey) {
if (d.type === 'vnfd') {
this.selectedNode.push(d);
}
this.mousedownNode = d;
this.currentSelectedNode = (this.mousedownNode === this.currentSelectedNode) ? null : this.mousedownNode;
this.currentSelectedLink = null;
this.dragLine.style('marker-end', 'url(#end-arrow)').classed('hidden', false)
.attr('d', `M${this.mousedownNode.x},${this.mousedownNode.y}L${this.mousedownNode.x},${this.mousedownNode.y}`);
}
}
/** Event handles when mouseup event occures @private */
private mouseUp(d: COMPOSERNODES): void {
if (!this.mousedownNode) { return; }
this.dragLine.classed('hidden', true).style('marker-end', '');
this.mouseupNode = d;
if (this.mousedownNode.type === 'vld' && this.mouseupNode.type === 'vnfd') {
const getOldVLDIndex: string[] = this.mouseupNode.id.split(':');
const setOldVLDindex: number = +getOldVLDIndex[1];
this.putType = 'cpAdded';
this.getAddConfirmation(this.mousedownNode, this.nsData, this.putType, setOldVLDindex);
} else if (this.mousedownNode.type === 'vnfd' && this.mouseupNode.type === 'vld') {
const getOldVLDIndex: string[] = this.mousedownNode.id.split(':');
const setOldVLDindex: number = +getOldVLDIndex[1];
this.putType = 'cpAdded';
this.getAddConfirmation(this.mousedownNode, this.nsData, this.putType, setOldVLDindex);
} else if (this.mousedownNode.type === 'vnfd' && this.mouseupNode.type === 'ns') {
this.deselectPath();
this.notifierService.notify('warning', this.translateService.instant('PAGE.NSPACKAGE.NSCOMPOSE.CANNOTLINKVNFCP'));
} else if (this.mousedownNode.type === 'vld' && this.mouseupNode.type === 'ns') {
this.deselectPath();
this.notifierService.notify('warning', this.translateService.instant('PAGE.NSPACKAGE.NSCOMPOSE.CANNOTLINKVNFCP'));
} else if (this.mousedownNode.type === 'vld' && this.mouseupNode.type === 'vld') {
this.deselectPath();
this.notifierService.notify('warning', this.translateService.instant('PAGE.NSPACKAGE.NSCOMPOSE.CANNOTLINKVL'));
} else if (this.mousedownNode.type === 'vnfd' && this.mouseupNode.type === 'vnfd') {
this.deselectPath();
this.notifierService.notify('warning', this.translateService.instant('PAGE.NSPACKAGE.NSCOMPOSE.CANNOTLINKVNF'));
} else if (this.mousedownNode.type === 'ns' && this.mouseupNode.type === 'ns') {
this.deselectPath();
this.notifierService.notify('warning', this.translateService.instant('PAGE.NSPACKAGE.NSCOMPOSE.CANNOTLINKCP'));
} else {
this.deselectPath();
this.notifierService.notify('warning', this.translateService.instant('PAGE.NSPACKAGE.NSCOMPOSE.CANNOTLINKVLVNF'));
}
this.resetMouseVars();
// select new link
this.currentSelectedLink = d;
this.currentSelectedNode = null;
}
/** Mosue Drag Line false if it is not satisfied @private */
private deselectPath(): void {
this.dragLine.classed('hidden', true).style('marker-end', '').attr('d', 'M0,0L0,0');
}
/** reset Mouse varaibles @private */
private resetMouseVars(): void {
this.mousedownNode = null;
this.mouseupNode = null;
this.mousedownLink = null;
}
/** De-select all the selected nodes @private */
private deselectAllNodes(): void {
this.vlNode.select('image').classed(this.activeClass, false);
this.vnfdnode.select('image').classed(this.activeClass, false);
this.cpnode.select('image').classed(this.activeClass, false);
}
/** Show the right-side information @private */
private showRightSideInfo(nsdDetails: boolean, vldDetails: boolean, vnfDeails: boolean, cpDetails: boolean): void {
this.isShowNSDDetails = nsdDetails;
this.isShowVLDetails = vldDetails;
this.isShowVNFDetails = vnfDeails;
this.isShowCPDetails = cpDetails;
}
/** Events handles when Shift Click to perform create cp @private */
// tslint:disable-next-line: no-any
private singleClick(nodeSelected: any, d: COMPOSERNODES): void {
this.selectNodeExclusive(nodeSelected, d);
}
/** Selected nodes @private */
// tslint:disable-next-line: no-any
private selectNodeExclusive(nodeSeleced: any, d: COMPOSERNODES): void {
const alreadyIsActive: boolean = nodeSeleced.select('#' + d.selectorId).classed(this.activeClass);
this.deselectAllNodes();
nodeSeleced.select('#' + d.selectorId).classed(this.activeClass, !alreadyIsActive);
if (d.type === 'vld' && !alreadyIsActive) {
this.nsData.vld.forEach((result: VLD) => {
if (result.id === d.id) {
this.showRightSideInfo(false, true, false, false);
this.vlDetails = result;
}
});
} else if (d.type === 'vnfd' && !alreadyIsActive) {
this.nsData['constituent-vnfd'].forEach((result: CONSTITUENTVNFD) => {
if (result['member-vnf-index'] === d.nodeIndex && result['vnfd-id-ref'] === d.name) {
this.showRightSideInfo(false, false, true, false);
this.vnfData = result;
}
});
} else if (d.type === 'ns' && !alreadyIsActive) {
this.nsData.vld.forEach((result: VLD) => {
if (result['vnfd-connection-point-ref'] !== undefined) {
result['vnfd-connection-point-ref'].forEach((resultCP: VNFDCONNECTIONPOINTREF) => {
if (resultCP['member-vnf-index-ref'] === d.nodeIndex && resultCP['vnfd-connection-point-ref'] === d.name) {
this.cpData = resultCP;
this.vlDetails = result;
this.showRightSideInfo(false, false, false, true);
}
});
}
});
} else {
this.showRightSideInfo(true, false, false, false);
}
}
/** Get confirmation Before Deleting the Link in Topology @private */
private getDeleteLinkConfirmation(d: Tick): void {
const modalRef: NgbModalRef = this.modalService.open(ConfirmationTopologyComponent, { backdrop: 'static' });
modalRef.componentInstance.topologyType = 'Delete';
modalRef.componentInstance.topologyname = this.translateService.instant('PAGE.TOPOLOGY.LINK');
modalRef.componentInstance.topologytitle = 'PAGE.TOPOLOGY.LINK';
modalRef.result.then((result: MODALCLOSERESPONSEDATA) => {
if (result) {
this.doubleClickLink(d);
}
}).catch();
}
/** Events handles when Double Click to Delete the link @private */
private doubleClickLink(d: Tick): void {
let getID: string = '';
if (d.target.type === 'vld') {
getID = d.target.id;
} else if (d.source.type === 'vld') {
getID = d.source.id;
}
this.nodes.forEach((res: COMPOSERNODES) => {
if (res.id === getID) {
if (this.nsData.vld !== undefined) {
this.nsData.vld.forEach((vldresult: VLD) => {
if (vldresult.id === getID) {
delete vldresult['vnfd-connection-point-ref'];
}
});
}
}
});
this.putType = 'linkdelete';
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, this.nsData, this.putType);
}
/** Get confirmation Before Deleting the Node in Topology @private */
private getDeleteConfirmation(d: COMPOSERNODES): void {
const modalRef: NgbModalRef = this.modalService.open(ConfirmationTopologyComponent, { backdrop: 'static' });
modalRef.componentInstance.topologyType = 'Delete';
modalRef.componentInstance.topologyname = d.name;
if (d.type === 'vld') {
modalRef.componentInstance.topologytitle = 'PAGE.TOPOLOGY.VIRTUALLINK';
} else if (d.type === 'vnfd') {
modalRef.componentInstance.topologytitle = 'PAGE.TOPOLOGY.VNF';
} else if (d.type === 'ns') {
modalRef.componentInstance.topologytitle = 'PAGE.TOPOLOGY.CONNECTIONPOINT';
}
modalRef.result.then((result: MODALCLOSERESPONSEDATA) => {
if (result) {
this.doubleClick(d);
}
}).catch();
}
/** Events handles when Double Click to Delete @private */
private doubleClick(d: COMPOSERNODES): void {
const deletedNode: COMPOSERNODES = d;
this.nodes.forEach((res: COMPOSERNODES) => {
if (res.id === d.id) {
if (deletedNode.type === 'vld') {
const pos: number = this.nsData.vld.map((e: VLD) => { return e.id; }).indexOf(d.id);
this.nsData.vld.splice(pos, 1);
this.putType = 'nsddelete';
} else if (deletedNode.type === 'vnfd') {
const constituentVNFD: string[] = [];
if (this.nsData['constituent-vnfd'] !== undefined) {
this.nsData['constituent-vnfd'].forEach((ref: CONSTITUENTVNFD) => {
constituentVNFD.push(ref['vnfd-id-ref'] + ':' + ref['member-vnf-index']);
});
}
const pos: number = constituentVNFD.map((e: string) => { return e; }).indexOf(d.id);
this.nsData['constituent-vnfd'].splice(pos, 1);
const getCP: string[] = d.id.split(':');
const memberVnfIndexRef: number = +getCP[1];
const vnfdIDRef: string = getCP[0];
if (this.nsData.vld !== undefined) {
this.nsData.vld.forEach((resf: VLD) => {
if (resf['vnfd-connection-point-ref'] !== undefined) {
resf['vnfd-connection-point-ref'].forEach((connectionPoint: VNFDCONNECTIONPOINTREF, index: number) => {
if (+connectionPoint['member-vnf-index-ref'] === memberVnfIndexRef && connectionPoint['vnfd-id-ref'] === vnfdIDRef) {
resf['vnfd-connection-point-ref'].splice(index, 1);
}
});
}
});
}
this.putType = 'vnfddelete';
} else if (deletedNode.type === 'ns') {
const getCP: string[] = d.selectorId.split('-osm-');
const memberVnfIndexRef: number = d.nodeIndex;
const vnfdIDRef: string = getCP[getCP.length - 1];
if (this.nsData.vld !== undefined) {
this.nsData.vld.forEach((resf: VLD) => {
if (resf['vnfd-connection-point-ref'] !== undefined && resf.id === vnfdIDRef) {
resf['vnfd-connection-point-ref'].forEach((connectionPoint: VNFDCONNECTIONPOINTREF, index: number) => {
if (connectionPoint['member-vnf-index-ref'] === memberVnfIndexRef) {
resf['vnfd-connection-point-ref'].splice(index, 1);
}
});
}
});
}
this.putType = 'nsdelete';
}
this.addData(environment.NSDESCRIPTORS_URL, this.identifier, this.nsData, this.putType);
}
});
}
/** Key press event @private */
private keyDown(): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
if (this.lastKeyDown !== -1) { return; }
this.lastKeyDown = d3.event.keyCode;
if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
this.gvlNode.call(d3.drag()
.on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
);
this.gvnfdNode.call(d3.drag()
.on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
);
this.gcpNode.call(d3.drag()
.on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
);
this.svg.classed('ctrl', true);
}
}
/** Key realse event @private */
private keyUp(): void {
const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
this.lastKeyDown = -1;
if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
this.gvlNode.on('.drag', null);
this.gvnfdNode.on('.drag', null);
this.gcpNode.on('.drag', null);
this.svg.classed('ctrl', false);
}
}
/** Events handles when dragstarted @private */
private dragstarted(d: COMPOSERNODES): void {
d.fx = d.x;
d.fy = d.y;
}
/** Events handles when dragged @private */
private dragged(d: COMPOSERNODES): void {
d.fx = d.x = d3.event.x;
d.fy = d.y = d3.event.y;
}
/** Events handles when dragended @private */
private dragended(d: COMPOSERNODES): void {
if (this.forceSimulationActive) {
d.fx = null;
d.fy = null;
} else {
d.fx = d.x;
d.fy = d.y;
this.forceSimulationActive = false;
}
}
/** Events handles when node double click @private */
private onNodedblClickToggleSidebar(): void {
this.sideBarOpened = false;
}
/** Events handles when node single click @private */
private onNodeClickToggleSidebar(): void {
this.sideBarOpened = true;
}
}