blob: 07f85ca3ea73a0e72965df07b8af3b8246be7d67 [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -070019(function () {
20 'use strict';
21
22 angular.module('xos.mcord-slicing')
23 .directive('slicingTopo', function(){
24 return {
25 restrict: 'E',
26 scope: {},
27 bindToController: true,
28 controllerAs: 'vm',
29 templateUrl: 'templates/slicing-topo.tpl.html',
Matteo Scandolo4d121c22016-10-31 13:20:31 +010030 controller: function($element, SliceGraph, McordSlicingTopo, _, NodePositioner, FormHandler, mCordSlicingIcons){
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -070031
32 let svg;
33 let nodes, links;
34 let nodeGroup, linkGroup, formGroup;
35 let dragLine, dragStartNode, dragEndNode, selectedLink;
36 let selectedNode, nodeSiblings;
37
38 var t = d3.transition()
39 .duration(500);
40
41 this.activeSlices = [];
42
43 const resetDragInfo = () => {
44 // reset drag nodes
45 dragStartNode = null;
46 dragEndNode = null;
47
48 // hide dragLine
49 dragLine
50 .classed('hidden', true);
51 };
52
53 McordSlicingTopo.query().$promise
54 .then((topology) => {
55 NodePositioner.storeEl($element[0]);
56 handleSvg($element[0]);
57 SliceGraph.buildGraph(topology);
58 _nodes = SliceGraph.positionGraph($element[0]);
59 _links = SliceGraph.getGraphLinks(_nodes);
60 drawGraph();
61 })
62 .catch((e) => {
63 throw new Error(e);
64 });
65
66 const handleSvg = (el) => {
67 this.el = el;
68 d3.select(el).select('svg').remove();
69
70 svg = d3.select(el)
71 .append('svg')
72 .style('width', `${el.clientWidth}px`)
73 .style('height', `${el.clientHeight}px`);
74
75 linkGroup = svg.append('g')
76 .attr({
77 class: 'link-group'
78 });
79
80 nodeGroup = svg.append('g')
81 .attr({
82 class: 'node-group'
83 });
84
85 formGroup = d3.select(el)
86 .append('div')
87 .attr({
88 class: 'form-container'
89 });
90
91 // line displayed when dragging nodes
92 dragLine = svg.append('svg:path')
93 .attr('class', 'dragline hidden')
94 .attr('d', 'M0,0L0,0');
95 };
96
97 const tick = () => {
Matteo Scandolo4d121c22016-10-31 13:20:31 +010098
99 // svg.selectAll('.node')
100 // .attr({
101 // y: (n) => {
102 // console.log(n.y);
103 // return n.y;
104 // }
105 // });
106
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -0700107 svg.selectAll('.link')
108 .attr('x1', d => d.source.x)
109 .attr('y1', d => d.source.y)
110 .attr('x2', d => d.target.x)
111 .attr('y2', d => d.target.y);
112 };
113
114 // prepare the data to show all slices
115 let _nodes = [];
116 let _links = [];
117
118 // attach slice details
119 const attachSliceDetails = n => {
120 let [newNodes, newLinks] = SliceGraph.getSliceDetail(n);
121 _nodes = _nodes.concat(newNodes);
122 _links = _links.concat(newLinks);
123 drawGraph();
124 };
125
126 // remove slice detail
127 const removeSliceDetails = sliceId => {
128
129 SliceGraph.removeActiveSlice(sliceId);
130
131 // if the selected node is part of the slice I'm closing
132 // deselect the node
133 if(selectedNode && selectedNode.sliceId === sliceId){
134 selectedNode = null;
135 nodeSiblings = null;
136 }
137
138 // remove control plane nodes related to this slice
139 _nodes = _.filter(_nodes, n => {
140 if(n.sliceId === sliceId && (n.plane === 'control' || n.type === 'button')){
141 // if I remove the node check that there is no form attached
142 FormHandler.removeFormByParentNode(n, linkGroup, formGroup);
143 return false;
144 }
145 return true;
146 });
147
148 // remove sliceId from data plane element
149 _nodes = _.map(_nodes, n => {
150 if(n.sliceId === sliceId){
151 delete n.sliceId;
152 }
153 return n;
154 });
155
156 // remove control plane links related to this slice
157 _links = _.filter(_links, l => {
158 if(_.findIndex(_nodes, {id: l.data.source}) === -1){
159 return false;
160 }
161 if(_.findIndex(_nodes, {id: l.data.target}) === -1){
162 return false;
163 }
164 return true;
165 });
166 drawGraph();
167 };
168
169 const deleteLink = linkId => {
170 // TODO
171 // [ ] delete from graphlib
172 // [ ] delete from backend
173 console.log(_links);
174 _.remove(_links, (l) => {
175 return l.data.id === linkId;
176 });
177 console.log(_links);
178 drawGraph();
179 };
180
181 const expandNode = (n) => {
182 console.log('exp', n);
183 resetDragInfo();
184 const sliceComponents = ['ran-ru', 'ran-cu', 'pgw', 'sgw'];
185 if(sliceComponents.indexOf(n.type) > -1 && n.plane === 'data' && !n.sliceId){
186 attachSliceDetails(n);
187 }
188 else if (n.type === 'button'){
189 removeSliceDetails(n.sliceId);
190 }
191 else if (!n.formAttached && n.model){
192 n.formAttached = true;
193 FormHandler.drawForm(n, linkGroup, formGroup);
194 }
195 else if (n.formAttached){
196 n.formAttached = false;
197 FormHandler.removeFormByParentNode(n, linkGroup, formGroup);
198 }
199 };
200
201 const selectNextNode = () => {
202 if(!selectedNode){
203 selectedNode = _nodes[0];
204 selectedNode.selected = true;
205 }
206 else {
207 // TODO if no sliceId check only data plane successors
208 nodeSiblings = SliceGraph.getNodeSuccessors(selectedNode);
209
210 if(nodeSiblings.length === 0){
211 return;
212 };
213 // reset current selected node
214 selectedNode.selected = false;
215 // find next node
216 let nextNodeId = _.findIndex(_nodes, {id: nodeSiblings[0].id});
217 selectedNode = _nodes[nextNodeId];
218 selectedNode.selected = true;
219
220 // NOTE I need to update sibling with successor of the parent
221 // to enable vertical navigation
222 let parents = SliceGraph.getNodeSuccessors(selectedNode);
223 if(parents.lenght > 0){
224 nodeSiblings = SliceGraph.getNodePredecessors(parents[0]);
225 }
226 else {
227 nodeSiblings = null;
228 }
229 }
230 drawGraph();
231 };
232
233 const selectPrevNode = () => {
234 if(!selectedNode){
235 selectedNode = _nodes[0];
236 selectedNode.selected = true;
237 }
238 else {
239 nodeSiblings = SliceGraph.getNodePredecessors(selectedNode);
240
241 if(nodeSiblings.length === 0){
242 return;
243 };
244 // reset current selected node
245 selectedNode.selected = false;
246 // find next node
247 let prev = _.findIndex(_nodes, {id: nodeSiblings[0].id});
248
249 if(prev < 0){
250 prev = _nodes.length - 1;
251 }
252 selectedNode = _nodes[prev];
253 selectedNode.selected = true;
254 }
255 drawGraph();
256 };
257
258 const sortByY = (a, b) => {
259 if (a.y < b.y)
260 return 1;
261 if (a.y > b.y)
262 return -1;
263 return 0;
264 };
265
266 const getSameTypeNodes = (selectedNode) => {
267 return _.filter(_nodes, n => {
268 if(selectedNode.type === 'pgw' && n.type === 'button'){
269 return true;
270 }
271 if(selectedNode.type === 'button' && n.type === 'pgw'){
272 return true;
273 }
274 if (selectedNode.type === 'sgw' && n.type === 'mme'){
275 return true;
276 }
277 if (selectedNode.type === 'mme' && n.type === 'sgw'){
278 return true;
279 }
280 return n.type === selectedNode.type;
281 }).sort(sortByY);
282 };
283
284 const selectNextSibling = () => {
285 if(!selectedNode){
286 selectedNode = _nodes[0];
287 selectedNode.selected = true;
288 }
289 else {
290 // reset current selected node
291 selectedNode.selected = false;
292
293 // find next node
294 let sameTypeNodes = getSameTypeNodes(selectedNode);
295
296 let nextSiblingId = _.findIndex(sameTypeNodes, {id: selectedNode.id}) + 1;
297 if(nextSiblingId === sameTypeNodes.length){
298 nextSiblingId = 0;
299 }
300 let nextNodeId = _.findIndex(_nodes, {id: sameTypeNodes[nextSiblingId].id});
301 selectedNode = _nodes[nextNodeId];
302 selectedNode.selected = true;
303 }
304 drawGraph();
305 };
306
307 const selectPrevSibling = () => {
308 if(!selectedNode){
309 selectedNode = _nodes[0];
310 selectedNode.selected = true;
311 }
312 else {
313 // reset current selected node
314 selectedNode.selected = false;
315
316 // find next node
317 let sameTypeNodes = getSameTypeNodes(selectedNode);
318
319 let nextSiblingId = _.findIndex(sameTypeNodes, {id: selectedNode.id}) - 1;
320 if(nextSiblingId < 0){
321 nextSiblingId = sameTypeNodes.length - 1;
322 }
323 let nextNodeId = _.findIndex(_nodes, {id: sameTypeNodes[nextSiblingId].id});
324 selectedNode = _nodes[nextNodeId];
325 selectedNode.selected = true;
326 }
327 drawGraph();
328 };
329
330 const drawGraph = () => {
331
332 // svg.selectAll('.node-container').remove();
333 // svg.selectAll('.link-container').remove();
334
335 var force = d3.layout.force()
336 .nodes(_nodes)
337 .links(_links)
338 .charge(-1060)
339 .gravity(0.1)
340 .linkDistance(200)
341 .size([this.el.clientWidth, this.el.clientHeight])
342 .on('tick', tick)
343 .start();
344
345 links = linkGroup.selectAll('.link-container')
346 .data(_links, d => d.data.id)
347 .enter()
348 .insert('g')
349 .attr({
350 class: 'link-container',
351 opacity: 0
352 });
353
354 links
355 .transition(t)
356 .attr({
357 opacity: 1
358 });
359
360 links.insert('line')
361 .attr('class', d => `link ${d.data.plane}`)
362 .on('click', function(link ){
363 selectedLink = link;
364
365 // deselect all other links
366 d3.selectAll('.link').classed('selected', false);
367
368 d3.select(this).classed('selected', true);
369 });
370
371 nodes = nodeGroup.selectAll('.node')
372 .data(_nodes, d => d.id)
373 .attr({
374 class: d => `node ${d.plane} ${d.type} ${d.selected ? 'selected': ''}`,
375 });
376
377 nodes
378 .enter()
379 .append('g')
380 .attr({
381 class: 'node-container',
382 transform: d => d.transform,
383 opacity: 0
384 });
385
386 nodes.transition(t)
387 .attr({
388 opacity: 1
389 });
390
391 nodes.append('rect')
392 .attr({
393 class: d => `node ${d.plane} ${d.type} ${d.selected ? 'selected': ''}`,
394 width: 100,
395 height: 50,
396 x: -50,
397 y: -25
398 });
399
400 nodes.append('text')
401 .attr({
Matteo Scandolo4d121c22016-10-31 13:20:31 +0100402 'text-anchor': 'left',
403 'alignment-baseline': 'middle',
404 x: -20
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -0700405 })
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -0700406 .text(d => `${d.name}`);
407
408 nodes.on('click', (n) => {
409 expandNode(n);
410 });
411
Matteo Scandolo4d121c22016-10-31 13:20:31 +0100412 // draw icons
413 const ues = nodes.filter(n => n.type === 'ue');
414 ues.append('path')
415 .attr({
416 d: mCordSlicingIcons.mobile,
417 class: 'icon',
418 transform: `translate(-40, -12.5), scale(0.5)`
419 });
420
421 const profiles = nodes.filter(n => n.type === 'profile');
422 profiles.append('path')
423 .attr({
424 d: mCordSlicingIcons.profile,
425 class: 'icon',
426 transform: `translate(-40, -12.5), scale(0.5)`
427 });
428
429 const rru = nodes.filter(n => n.type === 'ran-ru');
430 rru.append('path')
431 .attr({
432 d: mCordSlicingIcons.rru,
433 class: 'icon',
434 transform: `translate(-40, -12.5), scale(0.5)`
435 });
436
437 const rcu = nodes.filter(n => n.type === 'ran-cu');
438 rcu.append('path')
439 .attr({
440 d: mCordSlicingIcons.rcu,
441 class: 'icon',
442 transform: `translate(-40, -12.5), scale(0.5)`
443 });
444
445 const sgw = nodes.filter(n => n.type === 'sgw');
446 sgw.append('path')
447 .attr({
448 d: mCordSlicingIcons.sgw,
449 class: 'icon',
450 transform: `translate(-40, -12.5), scale(0.5)`
451 });
452
453 const pgw = nodes.filter(n => n.type === 'pgw');
454 pgw.append('path')
455 .attr({
456 d: mCordSlicingIcons.pgw,
457 class: 'icon',
458 transform: `translate(-40, -12.5), scale(0.5)`
459 });
460
461 const mme = nodes.filter(n => n.type === 'mme');
462 mme.append('path')
463 .attr({
464 d: mCordSlicingIcons.mme,
465 class: 'icon',
466 transform: `translate(-40, -12.5), scale(0.5)`
467 });
468
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -0700469 nodes
470 .on('mousedown', (n) => {
471 // save a reference to dragStart
472 dragStartNode = n;
473
474 dragLine
475 .classed('hidden', false)
476 .attr('d', 'M' + dragStartNode.x + ',' + dragStartNode.y + 'L' + dragStartNode.x + ',' + dragStartNode.y);
477 })
478 .on('mouseover', (n) => {
479 if(dragStartNode){
480 dragEndNode = n;
481 }
482 });
483
484 svg
485 .on('mousemove', function(){
486 if(!dragStartNode){
487 return;
488 }
489 dragLine.attr('d', 'M' + dragStartNode.x + ',' + dragStartNode.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
490 })
491 .on('mouseup', () => {
492 if(!dragStartNode || !dragEndNode){
493 resetDragInfo();
494 return;
495 }
496
497 // TODO
498 // [X] check that I can connect the two nodes
499 // [X] check link direction
500 // [ ] save the new link in the BE
501
502 // check that I can connect the 2 nodes
503 const successorType = SliceGraph.getNodeDataPlaneSuccessors(dragStartNode)[0].type;
504 if(dragEndNode.type !== successorType){
505 resetDragInfo();
506 return;
507 }
508
509 // create the link
510 _links.push({
511 source: dragStartNode,
512 target: dragEndNode,
513 data: {
514 id: `${dragStartNode.id}.${dragEndNode.id}`,
515 source: dragStartNode.id,
516 target: dragEndNode.id
517 }
518 });
519
520 // update the graph
521 // TODO recalculate graph positions
522 drawGraph();
523
524 resetDragInfo();
525 });
526
527 // remove exiting nodes
528 svg.selectAll('.node-container')
529 .data(_nodes, d => d.id)
530 .exit()
531 .transition(t)
532 .attr({
533 opacity: 0
534 })
535 .remove();
536
537 // remove exiting links
538 svg.selectAll('.link-container')
539 .data(_links, d => d.data.id)
540 .exit()
541 .transition(t)
542 .attr({
543 opacity: 0
544 })
545 .remove();
546 };
547
548 d3.select('body')
549 .on('keydown', function(){
550 // console.log(d3.event.code);
551 if(d3.event.code === 'Backspace' && selectedLink){
552 // delete link
553 deleteLink(selectedLink.data.id);
554 }
555 if(d3.event.code === 'Enter' && selectedNode){
556 d3.event.preventDefault();
557 expandNode(selectedNode);
558 }
559 if(d3.event.code === 'Escape' && selectedNode){
560 selectedNode.selected = false;
561 selectedNode = null;
562 nodeSiblings = null;
563 drawGraph();
564 }
565 if(d3.event.code === 'ArrowRight'){
566 d3.event.preventDefault();
567 selectNextNode();
568 }
569 if(d3.event.code === 'ArrowLeft'){
570 d3.event.preventDefault();
571 selectPrevNode();
572 }
573 if(d3.event.code === 'ArrowUp'){
574 d3.event.preventDefault();
575 selectNextSibling();
576 }
577 if(d3.event.code === 'ArrowDown'){
578 d3.event.preventDefault();
579 selectPrevSibling();
580 }
581
582 });
583 }
584 }
585 });
586})();