Simon Hunt

GUI -- Added updateLink and removeLink event handling.

Change-Id: Iae2d1f47bd4849e8ac80bebe721a2aa0ad5f4964
1 +{
2 + "event": "updateLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 6,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "21",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "512 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "updateLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "21",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "80 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "removeLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "21",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "80 Gb"
13 + }
14 + }
15 +}
1 { 1 {
2 - "event": "doUiThing", 2 + "event": "noop",
3 "payload": { 3 "payload": {
4 "id": "xyyzy" 4 "id": "xyyzy"
5 } 5 }
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
11 "" 11 ""
12 ], 12 ],
13 "metaUi": { 13 "metaUi": {
14 - "x": 400, 14 + "x": 520,
15 - "y": 280 15 + "y": 350
16 } 16 }
17 } 17 }
18 } 18 }
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
11 "" 11 ""
12 ], 12 ],
13 "metaUi": { 13 "metaUi": {
14 - "x": 400, 14 + "x": 520,
15 - "y": 280 15 + "y": 350
16 } 16 }
17 } 17 }
18 } 18 }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 "dst": "of:0000ffffffff0008", 9 "dst": "of:0000ffffffff0008",
10 "dstPort": "20", 10 "dstPort": "20",
11 "props" : { 11 "props" : {
12 - "BW": "70 G" 12 + "BW": "70 Gb"
13 } 13 }
14 } 14 }
15 } 15 }
......
...@@ -10,13 +10,16 @@ ...@@ -10,13 +10,16 @@
10 "description": [ 10 "description": [
11 "1. add device [8] (offline)", 11 "1. add device [8] (offline)",
12 "2. add device [3] (offline)", 12 "2. add device [3] (offline)",
13 - "3. update device [8] (online)", 13 + "3. update device [8] (online, label3 change)",
14 - "4. update device [3] (online)", 14 + "4. update device [3] (online, label3 change)",
15 "5. add link [3] --> [8]", 15 "5. add link [3] --> [8]",
16 "6. add host (to [3])", 16 "6. add host (to [3])",
17 "7. add host (to [8])", 17 "7. add host (to [8])",
18 "8. update host[3] (IP now 10.0.0.13)", 18 "8. update host[3] (IP now 10.0.0.13)",
19 "9. update host[8] (IP now 10.0.0.17)", 19 "9. update host[8] (IP now 10.0.0.17)",
20 + "10. update link (increase width, update props)",
21 + "11. update link (reduce width, update props)",
22 + "12. remove link",
20 "" 23 ""
21 ] 24 ]
22 } 25 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -260,36 +260,6 @@ ...@@ -260,36 +260,6 @@
260 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden'); 260 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
261 } 261 }
262 262
263 - function updateDeviceLabel(d) {
264 - var label = niceLabel(deviceLabel(d)),
265 - node = d.el,
266 - box;
267 -
268 - node.select('text')
269 - .text(label)
270 - .style('opacity', 0)
271 - .transition()
272 - .style('opacity', 1);
273 -
274 - box = adjustRectToFitText(node);
275 -
276 - node.select('rect')
277 - .transition()
278 - .attr(box);
279 -
280 - node.select('image')
281 - .transition()
282 - .attr('x', box.x + config.icons.xoff)
283 - .attr('y', box.y + config.icons.yoff);
284 - }
285 -
286 - function updateHostLabel(d) {
287 - var label = hostLabel(d),
288 - host = d.el;
289 -
290 - host.select('text').text(label);
291 - }
292 -
293 function cycleLabels() { 263 function cycleLabels() {
294 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1) 264 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
295 ? 0 : deviceLabelIndex + 1; 265 ? 0 : deviceLabelIndex + 1;
...@@ -371,10 +341,10 @@ ...@@ -371,10 +341,10 @@
371 addLink: addLink, 341 addLink: addLink,
372 addHost: addHost, 342 addHost: addHost,
373 updateDevice: updateDevice, 343 updateDevice: updateDevice,
374 - updateLink: stillToImplement, 344 + updateLink: updateLink,
375 updateHost: updateHost, 345 updateHost: updateHost,
376 removeDevice: stillToImplement, 346 removeDevice: stillToImplement,
377 - removeLink: stillToImplement, 347 + removeLink: removeLink,
378 removeHost: stillToImplement, 348 removeHost: stillToImplement,
379 showPath: showPath 349 showPath: showPath
380 }; 350 };
...@@ -429,6 +399,18 @@ ...@@ -429,6 +399,18 @@
429 } 399 }
430 } 400 }
431 401
402 + function updateLink(data) {
403 + var link = data.payload,
404 + id = link.id,
405 + linkData = network.lookup[id];
406 + if (linkData) {
407 + $.extend(linkData, link);
408 + updateLinkState(linkData);
409 + } else {
410 + logicError('updateLink lookup fail. ID = "' + id + '"');
411 + }
412 + }
413 +
432 function updateHost(data) { 414 function updateHost(data) {
433 var host = data.payload, 415 var host = data.payload,
434 id = host.id, 416 id = host.id,
...@@ -441,6 +423,17 @@ ...@@ -441,6 +423,17 @@
441 } 423 }
442 } 424 }
443 425
426 + function removeLink(data) {
427 + var link = data.payload,
428 + id = link.id,
429 + linkData = network.lookup[id];
430 + if (linkData) {
431 + removeLinkElement(linkData);
432 + } else {
433 + logicError('removeLink lookup fail. ID = "' + id + '"');
434 + }
435 + }
436 +
444 function showPath(data) { 437 function showPath(data) {
445 var links = data.payload.links, 438 var links = data.payload.links,
446 s = [ data.event + "\n" + links.length ]; 439 s = [ data.event + "\n" + links.length ];
...@@ -483,74 +476,81 @@ ...@@ -483,74 +476,81 @@
483 return 'translate(' + x + ',' + y + ')'; 476 return 'translate(' + x + ',' + y + ')';
484 } 477 }
485 478
479 + function missMsg(what, id) {
480 + return '\n[' + what + '] "' + id + '" missing ';
481 + }
482 +
483 + function linkEndPoints(srcId, dstId) {
484 + var srcNode = network.lookup[srcId],
485 + dstNode = network.lookup[dstId],
486 + sMiss = !srcNode ? missMsg('src', srcId) : '',
487 + dMiss = !dstNode ? missMsg('dst', dstId) : '';
488 +
489 + if (sMiss || dMiss) {
490 + logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
491 + return null;
492 + }
493 + return {
494 + source: srcNode,
495 + target: dstNode,
496 + x1: srcNode.x,
497 + y1: srcNode.y,
498 + x2: dstNode.x,
499 + y2: dstNode.y
500 + };
501 + }
502 +
486 function createHostLink(host) { 503 function createHostLink(host) {
487 var src = host.id, 504 var src = host.id,
488 dst = host.cp.device, 505 dst = host.cp.device,
489 id = host.ingress, 506 id = host.ingress,
490 - srcNode = network.lookup[src], 507 + lnk = linkEndPoints(src, dst);
491 - dstNode = network.lookup[dst],
492 - lnk;
493 508
494 - if (!dstNode) { 509 + if (!lnk) {
495 - logicError('switch not on map for link\n\n' +
496 - 'src = ' + src + '\ndst = ' + dst);
497 return null; 510 return null;
498 } 511 }
499 512
500 - // Compose link ... 513 + // Synthesize link ...
501 - lnk = { 514 + $.extend(lnk, {
502 id: id, 515 id: id,
503 - source: srcNode,
504 - target: dstNode,
505 class: 'link', 516 class: 'link',
506 type: 'hostLink', 517 type: 'hostLink',
507 svgClass: 'link hostLink', 518 svgClass: 'link hostLink',
508 - x1: srcNode.x, 519 + linkWidth: 1
509 - y1: srcNode.y, 520 + });
510 - x2: dstNode.x,
511 - y2: dstNode.y,
512 - width: 1
513 - }
514 return lnk; 521 return lnk;
515 } 522 }
516 523
517 function createLink(link) { 524 function createLink(link) {
518 - // start with the link object as is 525 + var lnk = linkEndPoints(link.src, link.dst),
519 - var lnk = link, 526 + type = link.type;
520 - type = link.type, 527 +
521 - src = link.src, 528 + if (!lnk) {
522 - dst = link.dst,
523 - w = link.linkWidth,
524 - srcNode = network.lookup[src],
525 - dstNode = network.lookup[dst];
526 -
527 - if (!(srcNode && dstNode)) {
528 - logicError('nodes not on map for link\n\n' +
529 - 'src = ' + src + '\ndst = ' + dst);
530 return null; 529 return null;
531 } 530 }
532 531
533 - // Augment as needed... 532 + // merge in remaining data
534 - $.extend(lnk, { 533 + $.extend(lnk, link, {
535 - source: srcNode,
536 - target: dstNode,
537 class: 'link', 534 class: 'link',
538 - svgClass: type ? 'link ' + type : 'link', 535 + svgClass: type ? 'link ' + type : 'link'
539 - x1: srcNode.x,
540 - y1: srcNode.y,
541 - x2: dstNode.x,
542 - y2: dstNode.y,
543 - width: w
544 }); 536 });
545 return lnk; 537 return lnk;
546 } 538 }
547 539
548 - function linkWidth(w) { 540 + var widthRatio = 1.4,
549 - // w is number of links between nodes. Scale appropriately. 541 + linkScale = d3.scale.linear()
550 - // TODO: use a d3.scale (linear, log, ... ?) 542 + .domain([1, 12])
551 - return w * 1.2; 543 + .range([widthRatio, 12 * widthRatio])
544 + .clamp(true);
545 +
546 + function updateLinkWidth (d) {
547 + // TODO: watch out for .showPath/.showTraffic classes
548 + d.el.transition()
549 + .duration(1000)
550 + .attr('stroke-width', linkScale(d.linkWidth));
552 } 551 }
553 552
553 +
554 function updateLinks() { 554 function updateLinks() {
555 link = linkG.selectAll('.link') 555 link = linkG.selectAll('.link')
556 .data(network.links, function (d) { return d.id; }); 556 .data(network.links, function (d) { return d.id; });
...@@ -572,7 +572,7 @@ ...@@ -572,7 +572,7 @@
572 }) 572 })
573 .transition().duration(1000) 573 .transition().duration(1000)
574 .attr({ 574 .attr({
575 - 'stroke-width': function (d) { return linkWidth(d.width); }, 575 + 'stroke-width': function (d) { return linkScale(d.linkWidth); },
576 stroke: '#666' // TODO: remove explicit stroke, rather... 576 stroke: '#666' // TODO: remove explicit stroke, rather...
577 }); 577 });
578 578
...@@ -589,13 +589,20 @@ ...@@ -589,13 +589,20 @@
589 //link .foo() .bar() ... 589 //link .foo() .bar() ...
590 590
591 // operate on exiting links: 591 // operate on exiting links:
592 - // TODO: figure out how to remove the node 'g' AND its children 592 + // TODO: better transition (longer as a dashed, grey line)
593 link.exit() 593 link.exit()
594 + .attr({
595 + 'stroke-dasharray': '3, 3'
596 + })
597 + .style('opacity', 0.4)
594 .transition() 598 .transition()
595 - .duration(750) 599 + .duration(2000)
596 .attr({ 600 .attr({
597 - opacity: 0 601 + 'stroke-dasharray': '3, 12'
598 }) 602 })
603 + .transition()
604 + .duration(1000)
605 + .style('opacity', 0.0)
599 .remove(); 606 .remove();
600 } 607 }
601 608
...@@ -650,7 +657,6 @@ ...@@ -650,7 +657,6 @@
650 node.y = y || network.view.height() / 2; 657 node.y = y || network.view.height() / 2;
651 } 658 }
652 659
653 -
654 function iconUrl(d) { 660 function iconUrl(d) {
655 return 'img/' + d.type + '.png'; 661 return 'img/' + d.type + '.png';
656 } 662 }
...@@ -694,12 +700,48 @@ ...@@ -694,12 +700,48 @@
694 return (label && label.trim()) ? label : '.'; 700 return (label && label.trim()) ? label : '.';
695 } 701 }
696 702
703 + function updateDeviceLabel(d) {
704 + var label = niceLabel(deviceLabel(d)),
705 + node = d.el,
706 + box;
707 +
708 + node.select('text')
709 + .text(label)
710 + .style('opacity', 0)
711 + .transition()
712 + .style('opacity', 1);
713 +
714 + box = adjustRectToFitText(node);
715 +
716 + node.select('rect')
717 + .transition()
718 + .attr(box);
719 +
720 + node.select('image')
721 + .transition()
722 + .attr('x', box.x + config.icons.xoff)
723 + .attr('y', box.y + config.icons.yoff);
724 + }
725 +
726 + function updateHostLabel(d) {
727 + var label = hostLabel(d),
728 + host = d.el;
729 +
730 + host.select('text').text(label);
731 + }
732 +
697 function updateDeviceState(nodeData) { 733 function updateDeviceState(nodeData) {
698 nodeData.el.classed('online', nodeData.online); 734 nodeData.el.classed('online', nodeData.online);
699 updateDeviceLabel(nodeData); 735 updateDeviceLabel(nodeData);
700 // TODO: review what else might need to be updated 736 // TODO: review what else might need to be updated
701 } 737 }
702 738
739 + function updateLinkState(linkData) {
740 + updateLinkWidth(linkData);
741 + // TODO: review what else might need to be updated
742 + // update label, if showing
743 + }
744 +
703 function updateHostState(hostData) { 745 function updateHostState(hostData) {
704 updateHostLabel(hostData); 746 updateHostLabel(hostData);
705 // TODO: review what else might need to be updated 747 // TODO: review what else might need to be updated
...@@ -826,6 +868,25 @@ ...@@ -826,6 +868,25 @@
826 .remove(); 868 .remove();
827 } 869 }
828 870
871 + function find(id, array) {
872 + for (var idx = 0, n = array.length; idx < n; idx++) {
873 + if (array[idx].id === id) {
874 + return idx;
875 + }
876 + }
877 + return -1;
878 + }
879 +
880 + function removeLinkElement(linkData) {
881 + // remove from lookup cache
882 + delete network.lookup[linkData.id];
883 + // remove from links array
884 + var idx = find(linkData.id, network.links);
885 +
886 + network.links.splice(linkData.index, 1);
887 + // remove from SVG
888 + updateLinks();
889 + }
829 890
830 function tick() { 891 function tick() {
831 node.attr({ 892 node.attr({
......