2 Copyright 2020 TATA ELXSI
4 Licensed under the Apache License, Version 2.0 (the 'License');
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
16 Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
19 * @file NS Topology Component
22 import { Component, ElementRef, Injector, ViewChild, ViewEncapsulation } from '@angular/core';
23 import { ActivatedRoute } from '@angular/router';
24 import { Router } from '@angular/router';
25 import { TranslateService } from '@ngx-translate/core';
26 import { ERRORDATA } from 'CommonModel';
27 import * as d3 from 'd3';
28 import { environment } from 'environment';
29 import * as HttpStatus from 'http-status-codes';
30 import { CCI, DF, VLC, VNFPROFILE } from 'NSDModel';
31 import { COMPOSERNODES, CONNECTIONPOINT, NSD, NSDVLD, NSINFO, NSInstanceDetails, NSINSTANCENODES, VLINFO, VNFRINFO } from 'NSInstanceModel';
32 import { GRAPHDETAILS, Tick, TickPath } from 'NSTopologyModel';
33 import { RestService } from 'src/services/RestService';
34 import { isNullOrUndefined } from 'util';
38 * @Component takes NSTopologyComponent.html as template url
41 selector: 'app-ns-topology',
42 templateUrl: './NSTopologyComponent.html',
43 styleUrls: ['./NSTopologyComponent.scss'],
44 encapsulation: ViewEncapsulation.None
46 /** Exporting a class @exports NSTopologyComponent */
47 export class NSTopologyComponent {
48 /** Injector to invoke other services @public */
49 public injector: Injector;
50 /** View child contains graphContainer ref @public */
51 @ViewChild('graphContainer', { static: true }) public graphContainer: ElementRef;
52 /** Holds the basic information of NS @public */
53 public nsInfo: NSINFO;
54 /** Contains tranlsate instance @private */
55 public translateService: TranslateService;
56 /** Add the activeclass for the selected @public */
57 public activeClass: string = 'active';
58 /** Add the fixed class for the freeze @public */
59 public fixedClass: string = 'fixed';
60 /** Check the loading results @public */
61 public isLoadingResults: boolean = true;
62 /** Give the message for the loading @public */
63 public message: string = 'PLEASEWAIT';
64 /** Assign the forcesimulation active @public */
65 public forceSimulationActive: boolean = false;
66 /** Assign pinned class for the button when freezed @public */
67 public classApplied: boolean = false;
68 /** Contains sidebar open status @public */
69 public sideBarOpened: boolean = true;
70 /** Need to show the NS Details @public */
71 public isShowNSDetails: boolean = true;
72 /** Need to show the VL Details @public */
73 public isShowVLetails: boolean = false;
74 /** Need to show the VNFR Details @public */
75 public isShowVNFRDetails: boolean = false;
76 /** Show right side info of Virtual Link @public */
77 public virtualLink: VLINFO;
78 /** Show right side info of Virtual Link @public */
79 public vnfr: VNFRINFO;
81 /** Contains lastkeypressed instance @private */
82 private lastKeyDown: number = -1;
83 /** Instance of the rest service @private */
84 private restService: RestService;
85 /** Holds the instance of AuthService class of type AuthService @private */
86 private activatedRoute: ActivatedRoute;
87 /** Holds the NS Id @private */
88 private nsIdentifier: string;
89 /** Contains SVG attributes @private */
90 // eslint-disable-next-line @typescript-eslint/no-explicit-any
92 /** Contains forced node animations @private */
93 // eslint-disable-next-line @typescript-eslint/no-explicit-any
95 /** Contains path information of the node */
96 // eslint-disable-next-line @typescript-eslint/no-explicit-any
98 /** Contains node network @private */
99 // eslint-disable-next-line @typescript-eslint/no-explicit-any
100 private network: any;
101 /** Contains node square @private */
102 // eslint-disable-next-line @typescript-eslint/no-explicit-any
104 /** Contains node circle @private */
105 // eslint-disable-next-line @typescript-eslint/no-explicit-any
107 /** Contains the NS information @private */
108 private nsData: NSInstanceDetails;
109 /** Contains NDS information of a descriptors */
110 private nsdData: NSD;
111 /** Contains node information @private */
112 private nodes: NSINSTANCENODES[] = [];
113 /** Contains links information @private */
114 private links: {}[] = [];
115 /** holds cp count/iteration @private */
116 private cpCount: number;
117 /** VNFD nodes @private */
118 private vnfdNodes: {}[] = [];
119 /** VLD nodes @private */
120 private vldNodes: {}[] = [];
121 /** Connection CP nodes @private */
122 private cpNodes: {}[] = [];
123 /** Set timeout @private */
124 private TIMEOUT: number = 2000;
125 /** Rendered nodes represent vnf @private */
126 // eslint-disable-next-line @typescript-eslint/no-explicit-any
127 private gSquare: any;
128 /** Rendered nodes represent network @private */
129 // eslint-disable-next-line @typescript-eslint/no-explicit-any
130 private gNetwork: any;
131 /** Rendered nodes represent network @private */
132 // eslint-disable-next-line @typescript-eslint/no-explicit-any
133 private gCircle: any;
134 /** Service holds the router information @private */
135 private router: Router;
137 constructor(injector: Injector) {
138 this.injector = injector;
139 this.restService = this.injector.get(RestService);
140 this.activatedRoute = this.injector.get(ActivatedRoute);
141 this.translateService = this.injector.get(TranslateService);
142 this.router = this.injector.get(Router);
146 * Lifecyle Hooks the trigger before component is instantiate @public
148 public ngOnInit(): void {
149 this.nsIdentifier = this.activatedRoute.snapshot.paramMap.get('id');
152 /** Event to freeze the animation @public */
153 public onFreeze(): void {
154 this.classApplied = !this.classApplied;
155 const alreadyFixedIsActive: boolean = d3.select('svg#graphContainer').classed(this.fixedClass);
156 d3.select('svg#graphContainer').classed(this.fixedClass, !alreadyFixedIsActive);
157 if (alreadyFixedIsActive) {
160 this.forceSimulationActive = alreadyFixedIsActive;
161 this.nodes.forEach((d: COMPOSERNODES): void => {
162 d.fx = (alreadyFixedIsActive) ? null : d.x;
163 d.fy = (alreadyFixedIsActive) ? null : d.y;
165 if (alreadyFixedIsActive) {
166 this.force.restart();
169 /** Events handles when dragended @public */
170 public toggleSidebar(): void {
171 this.sideBarOpened = !this.sideBarOpened;
172 this.deselectAllNodes();
173 this.showRightSideInfo(true, false, false);
175 /** Get the default Configuration of containers @private */
176 private getGraphContainerAttr(): GRAPHDETAILS {
189 sourcePaddingYes: 17,
199 /** Show the right-side information @private */
200 private showRightSideInfo(nsDetails: boolean, vlDetails: boolean, vnfrDeails: boolean): void {
201 this.isShowNSDetails = nsDetails;
202 this.isShowVLetails = vlDetails;
203 this.isShowVNFRDetails = vnfrDeails;
205 /** De-select all the selected nodes @private */
206 private deselectAllNodes(): void {
207 this.square.select('image').classed(this.activeClass, false);
208 this.network.select('image').classed(this.activeClass, false);
209 this.circle.select('image').classed(this.activeClass, false);
211 /** Prepare all the information for node creation @private */
212 private generateData(): void {
213 this.restService.getResource(environment.NSINSTANCESCONTENT_URL + '/' + this.nsIdentifier)
214 .subscribe((nsData: NSInstanceDetails): void => {
215 this.nsData = nsData;
217 nsInstanceID: nsData._id,
219 nsOperationalStatus: nsData['operational-status'],
220 nsConfigStatus: nsData['config-status'],
221 nsDetailedStatus: nsData['detailed-status'],
222 nsResourceOrchestrator: nsData['resource-orchestrator']
224 if (this.nsData['constituent-vnfr-ref'] !== undefined) {
225 this.generateVNFRCPNodes();
227 if (this.nsData.vld !== undefined) {
228 this.generateVLDNetworkNodes();
230 setTimeout((): void => {
232 this.generateVNFDCP();
233 this.generateVLDCP();
234 this.isLoadingResults = false;
235 this.createNode(this.nodes, this.links);
237 }, (error: ERRORDATA): void => {
238 this.isLoadingResults = false;
239 if (error.error.status === HttpStatus.NOT_FOUND || error.error.status === HttpStatus.UNAUTHORIZED) {
240 this.router.navigateByUrl('404', { skipLocationChange: true }).catch((): void => {
241 // Catch Navigation Error
244 this.restService.handleError(error, 'get');
248 /** Fetching all the VNFR Information @private */
249 private generateVNFRCPNodes(): void {
250 this.nsData['constituent-vnfr-ref'].forEach((vnfdrID: string): void => {
251 this.restService.getResource(environment.VNFINSTANCES_URL + '/' + vnfdrID).subscribe((vndfrDetail: NSD): void => {
253 id: vndfrDetail['vnfd-ref'] + ':' + vndfrDetail['member-vnf-index-ref'],
255 cp: vndfrDetail['connection-point'],
256 vdur: vndfrDetail.vdur,
257 nsID: vndfrDetail['nsr-id-ref'],
258 vnfdID: vndfrDetail['vnfd-id'],
259 vimID: vndfrDetail['vim-account-id'],
260 vndfrID: vndfrDetail.id,
261 ipAddress: vndfrDetail['ip-address'],
262 memberIndex: vndfrDetail['member-vnf-index-ref'],
263 vnfdRef: vndfrDetail['vnfd-ref'],
264 selectorId: 'nsInst-' + vndfrDetail.id
266 // Fetching all the connection point of VNF & Interface
267 vndfrDetail['connection-point'].forEach((cp: CONNECTIONPOINT): void => {
269 id: cp.name + ':' + vndfrDetail['member-vnf-index-ref'],
270 vndfCPRef: vndfrDetail['vnfd-ref'] + ':' + vndfrDetail['member-vnf-index-ref'],
275 }, (error: ERRORDATA): void => {
276 this.restService.handleError(error, 'get');
280 /** Fetching all the VLD/Network Information @private */
281 private generateVLDNetworkNodes(): void {
282 this.nsdData = this.nsData.nsd;
283 this.nsdData['virtual-link-desc'].forEach((ref: NSDVLD): void => {
289 vnfdCP: this.nsdData.df,
290 vimNetworkName: ref['vim-network-name'],
291 selectorId: 'nsInst-' + ref.id
295 /** Pushing connection points of path/links nodes @private */
296 private pushAllNodes(): void {
297 this.nodes.forEach((nodeList: NSINSTANCENODES): void => {
298 if (nodeList.nodeTypeRef === 'vnfd') {
299 this.vnfdNodes.push(nodeList);
300 } else if (nodeList.nodeTypeRef === 'vld') {
301 this.vldNodes.push(nodeList);
302 } else if (nodeList.nodeTypeRef === 'cp') {
303 this.cpNodes.push(nodeList);
307 /** Get CP position based on vndf @private */
308 private generateVNFDCP(): void {
309 this.vnfdNodes.forEach((list: NSINSTANCENODES): void => {
310 const vndfPos: number = this.nodes.map((e: NSINSTANCENODES): string => { return e.id; }).indexOf(list.id);
312 this.nodes.forEach((res: NSINSTANCENODES): void => {
313 if (res.nodeTypeRef === 'cp' && res.vndfCPRef === list.id) {
314 this.links.push({ source: this.nodes[vndfPos], target: this.nodes[this.cpCount] });
320 /** Get CP position based on vld @private */
321 private generateVLDCP(): void {
322 let vldPos: number = 0;
323 this.vldNodes.forEach((list: NSINSTANCENODES): void => {
324 if (!isNullOrUndefined(list.vnfdCP)) {
325 list.vnfdCP.forEach((cpRef: DF): void => {
326 cpRef['vnf-profile'].forEach((vnfProfile: VNFPROFILE): void => {
327 vnfProfile['virtual-link-connectivity'].forEach((resultVLC: VLC, index: number): void => {
328 resultVLC['constituent-cpd-id'].forEach((resultCCI: CCI): void => {
330 this.nodes.forEach((res: NSINSTANCENODES): void => {
331 if (res.nodeTypeRef === 'cp' &&
332 res.id === resultCCI['constituent-cpd-id'] + ':' + resultCCI['constituent-base-element-id']) {
333 this.links.push({ source: this.nodes[vldPos], target: this.nodes[this.cpCount] });
345 /** Node is created and render at D3 region @private */
346 private createNode(nodes: NSINSTANCENODES[], links: {}[]): void {
347 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
348 d3.selectAll('svg#graphContainer > *').remove();
349 d3.select(window).on('keydown', (): void => { this.keyDown(); });
350 d3.select(window).on('keyup', (): void => { this.keyUp(); });
351 this.svg = d3.select('#graphContainer')
352 .attr('oncontextmenu', 'return false;')
353 .attr('width', graphContainerAttr.width)
354 .attr('height', graphContainerAttr.height);
355 this.force = d3.forceSimulation()
356 .force('charge', d3.forceManyBody().strength(graphContainerAttr.strength))
357 .force('link', d3.forceLink().id((d: TickPath): string => d.id).distance(graphContainerAttr.distance))
358 .force('center', d3.forceCenter(graphContainerAttr.width / graphContainerAttr.forcex,
359 graphContainerAttr.height / graphContainerAttr.forcey))
360 .force('x', d3.forceX(graphContainerAttr.width / graphContainerAttr.forcex))
361 .force('y', d3.forceY(graphContainerAttr.height / graphContainerAttr.forcey))
362 .on('tick', (): void => { this.tick(); });
363 // handles to link and node element groups
364 this.path = this.svg.append('svg:g').selectAll('path');
365 this.network = this.svg.append('svg:g').selectAll('network');
366 this.square = this.svg.append('svg:g').selectAll('rect');
367 this.circle = this.svg.append('svg:g').selectAll('circle');
368 this.restart(nodes, links);
370 /** Update force layout (called automatically each iteration) @private */
371 private tick(): void {
372 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
373 // draw directed edges with proper padding from node centers
374 this.path.attr('class', 'link').attr('d', (d: Tick): string => {
375 const deltaX: number = d.target.x - d.source.x;
376 const deltaY: number = d.target.y - d.source.y;
377 const dist: number = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
378 const normX: number = deltaX / dist;
379 const normY: number = deltaY / dist;
380 const sourcePadding: number = d.left ? graphContainerAttr.sourcePaddingYes : graphContainerAttr.sourcePaddingNo;
381 const targetPadding: number = d.right ? graphContainerAttr.targetPaddingYes : graphContainerAttr.targetPaddingNo;
382 const sourceX: number = d.source.x + (sourcePadding * normX);
383 const sourceY: number = d.source.y + (sourcePadding * normY);
384 const targetX: number = d.target.x - (targetPadding * normX);
385 const targetY: number = d.target.y - (targetPadding * normY);
386 return `M${sourceX},${sourceY}L${targetX},${targetY}`;
388 this.network.attr('transform', (t: TickPath): string => `translate(${t.x},${t.y})`);
389 this.square.attr('transform', (t: TickPath): string => `translate(${t.x},${t.y})`);
390 this.circle.attr('transform', (t: TickPath): string => `translate(${t.x},${t.y})`);
392 /** Update graph (called when needed) @private */
393 private restart(nodes: NSINSTANCENODES[], links: {}[]): void {
394 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
395 this.path = this.path.data(links);
396 const vnfdNodes: {}[] = []; const vldNodes: {}[] = []; const cpNodes: {}[] = []; // NB: Nodes are known by id, not by index!
397 nodes.forEach((nodeList: NSINSTANCENODES): void => {
398 if (nodeList.nodeTypeRef === 'vnfd') { vnfdNodes.push(nodeList); }
399 else if (nodeList.nodeTypeRef === 'vld') { vldNodes.push(nodeList); }
400 else if (nodeList.nodeTypeRef === 'cp') { cpNodes.push(nodeList); }
402 this.square = this.square.data(vnfdNodes, (d: COMPOSERNODES): string => d.id);
403 this.network = this.network.data(vldNodes, (d: COMPOSERNODES): string => d.id);
404 this.circle = this.circle.data(cpNodes, (d: COMPOSERNODES): string => d.id);
405 this.resetAndCreateNodes();
406 this.force.nodes(nodes).force('link').links(links); //Set the graph in motion
407 this.force.alphaTarget(graphContainerAttr.alphaTarget).restart();
409 /** Rest and create nodes @private */
410 private resetAndCreateNodes(): void {
411 this.path.exit().remove();
412 this.square.exit().remove();
413 this.network.exit().remove();
414 this.circle.exit().remove();
415 // eslint-disable-next-line @typescript-eslint/no-explicit-any
416 const gPath: any = this.path.enter().append('svg:path').attr('class', 'link');
420 this.square = this.gSquare.merge(this.square);
421 this.network = this.gNetwork.merge(this.network);
422 this.path = gPath.merge(this.path);
423 this.circle = this.gCircle.merge(this.circle);
425 /** Events handles when Shift Click to perform create cp @private */
426 // eslint-disable-next-line @typescript-eslint/no-explicit-any
427 private singleClick(nodeSelected: any, d: COMPOSERNODES): void {
428 this.selectNodeExclusive(nodeSelected, d);
430 /** Selected nodes @private */
431 // eslint-disable-next-line @typescript-eslint/no-explicit-any
432 private selectNodeExclusive(nodeSelected: any, d: COMPOSERNODES): void {
433 const alreadyIsActive: boolean = nodeSelected.select('#' + d.selectorId).classed(this.activeClass);
434 this.deselectAllNodes();
435 nodeSelected.select('#' + d.selectorId).classed(this.activeClass, !alreadyIsActive);
436 if (d.nodeTypeRef === 'vld' && !alreadyIsActive) {
441 shortName: d.shortName,
442 vimNetworkName: d.vimNetworkName
444 this.showRightSideInfo(false, true, false);
445 } else if (d.nodeTypeRef === 'vnfd' && !alreadyIsActive) {
454 memberIndex: d.memberIndex
456 this.showRightSideInfo(false, false, true);
458 this.showRightSideInfo(true, false, false);
461 /** Setting all the square/vnf attributes of nodes @private */
462 private getgSquare(): void {
463 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
464 this.gSquare = this.square.enter().append('svg:g');
465 this.gSquare.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
466 this.gSquare.append('svg:image')
468 .attr('x', graphContainerAttr.imageX)
469 .attr('y', graphContainerAttr.imageY)
470 .call(this.onDragDrop())
471 .attr('id', (d: COMPOSERNODES): string => { return d.selectorId; })
472 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
473 .attr('xlink:href', 'assets/images/VNFD.svg')
474 .on('click', (d: COMPOSERNODES): void => { this.singleClick(this.gSquare, d); this.onNodeClickToggleSidebar(); });
475 this.gSquare.append('svg:text')
476 .attr('class', 'node_text')
477 .attr('y', graphContainerAttr.textY)
478 .text((d: COMPOSERNODES): string => d.id);
480 /** Settings all the network attributes of nodes @private */
481 private getgNetwork(): void {
482 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
483 this.gNetwork = this.network.enter().append('svg:g');
484 this.gNetwork.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
485 this.gNetwork.append('svg:image')
487 .attr('x', graphContainerAttr.imageX)
488 .attr('y', graphContainerAttr.imageY)
489 .call(this.onDragDrop())
490 .attr('id', (d: COMPOSERNODES): string => { return d.selectorId; })
491 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
492 .attr('xlink:href', 'assets/images/VL.svg')
493 .on('click', (d: COMPOSERNODES): void => { this.singleClick(this.gNetwork, d); this.onNodeClickToggleSidebar(); });
494 this.gNetwork.append('svg:text')
495 .attr('class', 'node_text')
496 .attr('y', graphContainerAttr.textY)
497 .text((d: COMPOSERNODES): string => d.name);
499 /** Settings all the connection point attributes of nodes @private */
500 private getgCircle(): void {
501 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
502 this.gCircle = this.circle.enter().append('svg:g');
503 this.gCircle.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
504 this.gCircle.append('svg:image')
506 .attr('x', graphContainerAttr.imageX)
507 .attr('y', graphContainerAttr.imageY)
508 .call(this.onDragDrop())
509 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
510 .attr('xlink:href', 'assets/images/CP.svg');
511 this.gCircle.append('svg:text')
512 .attr('class', 'node_text')
513 .attr('y', graphContainerAttr.textY)
514 .text((d: COMPOSERNODES): string => d.name);
516 /** drag event @private */
517 // eslint-disable-next-line @typescript-eslint/no-explicit-any
518 private onDragDrop(): any {
519 return d3.drag().filter(this.dragFilter)
520 .on('start', this.dragstarted)
521 .on('drag', this.dragged)
522 .on('end', this.dragended);
524 /** Key press event @private */
525 private keyDown(): void {
526 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
527 if (this.lastKeyDown !== -1) { return; }
528 this.lastKeyDown = d3.event.keyCode;
529 if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
530 this.gSquare.call(d3.drag()
531 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
533 this.gNetwork.call(d3.drag()
534 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
536 this.gCircle.call(d3.drag()
537 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
539 this.svg.classed('ctrl', true);
542 /** Key realse event @private */
543 private keyUp(): void {
544 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
545 this.lastKeyDown = -1;
546 if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
547 this.gSquare.on('.drag', null);
548 this.gNetwork.on('.drag', null);
549 this.gCircle.on('.drag', null);
550 this.svg.classed('ctrl', false);
553 /** Events handles when to drag using filter for the keys @private */
554 private dragFilter(): boolean {
555 return d3.event.ctrlKey && !d3.event.button;
557 /** Events handles when dragstarted @private */
558 private dragstarted(d: COMPOSERNODES): void {
562 /** Events handles when dragged @private */
563 private dragged(d: COMPOSERNODES): void {
564 d.fx = d.x = d3.event.x;
565 d.fy = d.y = d3.event.y;
567 /** Events handles when dragended @private */
568 private dragended(d: COMPOSERNODES): void {
569 if (this.forceSimulationActive) {
575 this.forceSimulationActive = false;
578 /** Events handles when node single click @private */
579 private onNodeClickToggleSidebar(): void {
580 this.sideBarOpened = true;