From 3f03d4a14ea904db19463c82b94693b4810c49eb Mon Sep 17 00:00:00 2001 From: Simon Hunt Date: Mon, 10 Nov 2014 20:14:37 -0800 Subject: [PATCH] GUI -- Added updateLink and removeLink event handling. Change-Id: Iae2d1f47bd4849e8ac80bebe721a2aa0ad5f4964 --- .../webapp/json/ev/simple/ev_10_onos.json | 15 ++ .../webapp/json/ev/simple/ev_11_onos.json | 15 ++ .../webapp/json/ev/simple/ev_12_onos.json | 15 ++ .../simple/{ev_10_ui.json => ev_13_ui.json} | 2 +- .../main/webapp/json/ev/simple/ev_1_onos.json | 4 +- .../main/webapp/json/ev/simple/ev_3_onos.json | 4 +- .../main/webapp/json/ev/simple/ev_5_onos.json | 2 +- .../main/webapp/json/ev/simple/scenario.json | 7 +- web/gui/src/main/webapp/topo2.js | 237 +++++++++++------- 9 files changed, 205 insertions(+), 96 deletions(-) create mode 100644 web/gui/src/main/webapp/json/ev/simple/ev_10_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/simple/ev_11_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/simple/ev_12_onos.json rename web/gui/src/main/webapp/json/ev/simple/{ev_10_ui.json => ev_13_ui.json} (63%) diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_10_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_10_onos.json new file mode 100644 index 0000000000..f09cc9bc1b --- /dev/null +++ b/web/gui/src/main/webapp/json/ev/simple/ev_10_onos.json @@ -0,0 +1,15 @@ +{ + "event": "updateLink", + "payload": { + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20", + "type": "direct", + "linkWidth": 6, + "src": "of:0000ffffffff0003", + "srcPort": "21", + "dst": "of:0000ffffffff0008", + "dstPort": "20", + "props" : { + "BW": "512 Gb" + } + } +} diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_11_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_11_onos.json new file mode 100644 index 0000000000..447ded37f9 --- /dev/null +++ b/web/gui/src/main/webapp/json/ev/simple/ev_11_onos.json @@ -0,0 +1,15 @@ +{ + "event": "updateLink", + "payload": { + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20", + "type": "direct", + "linkWidth": 2, + "src": "of:0000ffffffff0003", + "srcPort": "21", + "dst": "of:0000ffffffff0008", + "dstPort": "20", + "props" : { + "BW": "80 Gb" + } + } +} diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_12_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_12_onos.json new file mode 100644 index 0000000000..96018f3359 --- /dev/null +++ b/web/gui/src/main/webapp/json/ev/simple/ev_12_onos.json @@ -0,0 +1,15 @@ +{ + "event": "removeLink", + "payload": { + "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20", + "type": "direct", + "linkWidth": 2, + "src": "of:0000ffffffff0003", + "srcPort": "21", + "dst": "of:0000ffffffff0008", + "dstPort": "20", + "props" : { + "BW": "80 Gb" + } + } +} diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_10_ui.json b/web/gui/src/main/webapp/json/ev/simple/ev_13_ui.json similarity index 63% rename from web/gui/src/main/webapp/json/ev/simple/ev_10_ui.json rename to web/gui/src/main/webapp/json/ev/simple/ev_13_ui.json index 188bc5892b..9d6e73741e 100644 --- a/web/gui/src/main/webapp/json/ev/simple/ev_10_ui.json +++ b/web/gui/src/main/webapp/json/ev/simple/ev_13_ui.json @@ -1,5 +1,5 @@ { - "event": "doUiThing", + "event": "noop", "payload": { "id": "xyyzy" } diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json index 9874ec785c..8656a90e71 100644 --- a/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json +++ b/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json @@ -11,8 +11,8 @@ "" ], "metaUi": { - "x": 400, - "y": 280 + "x": 520, + "y": 350 } } } diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_3_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_3_onos.json index 73013a4bd1..56651c0e2b 100644 --- a/web/gui/src/main/webapp/json/ev/simple/ev_3_onos.json +++ b/web/gui/src/main/webapp/json/ev/simple/ev_3_onos.json @@ -11,8 +11,8 @@ "" ], "metaUi": { - "x": 400, - "y": 280 + "x": 520, + "y": 350 } } } diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json index ac521c41e2..6f390b5ac1 100644 --- a/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json +++ b/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json @@ -9,7 +9,7 @@ "dst": "of:0000ffffffff0008", "dstPort": "20", "props" : { - "BW": "70 G" + "BW": "70 Gb" } } } diff --git a/web/gui/src/main/webapp/json/ev/simple/scenario.json b/web/gui/src/main/webapp/json/ev/simple/scenario.json index 19d6190836..5fb8869964 100644 --- a/web/gui/src/main/webapp/json/ev/simple/scenario.json +++ b/web/gui/src/main/webapp/json/ev/simple/scenario.json @@ -10,13 +10,16 @@ "description": [ "1. add device [8] (offline)", "2. add device [3] (offline)", - "3. update device [8] (online)", - "4. update device [3] (online)", + "3. update device [8] (online, label3 change)", + "4. update device [3] (online, label3 change)", "5. add link [3] --> [8]", "6. add host (to [3])", "7. add host (to [8])", "8. update host[3] (IP now 10.0.0.13)", "9. update host[8] (IP now 10.0.0.17)", + "10. update link (increase width, update props)", + "11. update link (reduce width, update props)", + "12. remove link", "" ] } \ No newline at end of file diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js index f6a8456e40..94b2e9ed0e 100644 --- a/web/gui/src/main/webapp/topo2.js +++ b/web/gui/src/main/webapp/topo2.js @@ -260,36 +260,6 @@ bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden'); } - function updateDeviceLabel(d) { - var label = niceLabel(deviceLabel(d)), - node = d.el, - box; - - node.select('text') - .text(label) - .style('opacity', 0) - .transition() - .style('opacity', 1); - - box = adjustRectToFitText(node); - - node.select('rect') - .transition() - .attr(box); - - node.select('image') - .transition() - .attr('x', box.x + config.icons.xoff) - .attr('y', box.y + config.icons.yoff); - } - - function updateHostLabel(d) { - var label = hostLabel(d), - host = d.el; - - host.select('text').text(label); - } - function cycleLabels() { deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1) ? 0 : deviceLabelIndex + 1; @@ -371,10 +341,10 @@ addLink: addLink, addHost: addHost, updateDevice: updateDevice, - updateLink: stillToImplement, + updateLink: updateLink, updateHost: updateHost, removeDevice: stillToImplement, - removeLink: stillToImplement, + removeLink: removeLink, removeHost: stillToImplement, showPath: showPath }; @@ -429,6 +399,18 @@ } } + function updateLink(data) { + var link = data.payload, + id = link.id, + linkData = network.lookup[id]; + if (linkData) { + $.extend(linkData, link); + updateLinkState(linkData); + } else { + logicError('updateLink lookup fail. ID = "' + id + '"'); + } + } + function updateHost(data) { var host = data.payload, id = host.id, @@ -441,6 +423,17 @@ } } + function removeLink(data) { + var link = data.payload, + id = link.id, + linkData = network.lookup[id]; + if (linkData) { + removeLinkElement(linkData); + } else { + logicError('removeLink lookup fail. ID = "' + id + '"'); + } + } + function showPath(data) { var links = data.payload.links, s = [ data.event + "\n" + links.length ]; @@ -483,74 +476,81 @@ return 'translate(' + x + ',' + y + ')'; } + function missMsg(what, id) { + return '\n[' + what + '] "' + id + '" missing '; + } + + function linkEndPoints(srcId, dstId) { + var srcNode = network.lookup[srcId], + dstNode = network.lookup[dstId], + sMiss = !srcNode ? missMsg('src', srcId) : '', + dMiss = !dstNode ? missMsg('dst', dstId) : ''; + + if (sMiss || dMiss) { + logicError('Node(s) not on map for link:\n' + sMiss + dMiss); + return null; + } + return { + source: srcNode, + target: dstNode, + x1: srcNode.x, + y1: srcNode.y, + x2: dstNode.x, + y2: dstNode.y + }; + } + function createHostLink(host) { var src = host.id, dst = host.cp.device, id = host.ingress, - srcNode = network.lookup[src], - dstNode = network.lookup[dst], - lnk; + lnk = linkEndPoints(src, dst); - if (!dstNode) { - logicError('switch not on map for link\n\n' + - 'src = ' + src + '\ndst = ' + dst); + if (!lnk) { return null; } - // Compose link ... - lnk = { + // Synthesize link ... + $.extend(lnk, { id: id, - source: srcNode, - target: dstNode, class: 'link', type: 'hostLink', svgClass: 'link hostLink', - x1: srcNode.x, - y1: srcNode.y, - x2: dstNode.x, - y2: dstNode.y, - width: 1 - } - return lnk; - } - - function createLink(link) { - // start with the link object as is - var lnk = link, - type = link.type, - src = link.src, - dst = link.dst, - w = link.linkWidth, - srcNode = network.lookup[src], - dstNode = network.lookup[dst]; - - if (!(srcNode && dstNode)) { - logicError('nodes not on map for link\n\n' + - 'src = ' + src + '\ndst = ' + dst); - return null; - } - - // Augment as needed... - $.extend(lnk, { - source: srcNode, - target: dstNode, - class: 'link', - svgClass: type ? 'link ' + type : 'link', - x1: srcNode.x, - y1: srcNode.y, - x2: dstNode.x, - y2: dstNode.y, - width: w + linkWidth: 1 }); return lnk; } - function linkWidth(w) { - // w is number of links between nodes. Scale appropriately. - // TODO: use a d3.scale (linear, log, ... ?) - return w * 1.2; + function createLink(link) { + var lnk = linkEndPoints(link.src, link.dst), + type = link.type; + + if (!lnk) { + return null; + } + + // merge in remaining data + $.extend(lnk, link, { + class: 'link', + svgClass: type ? 'link ' + type : 'link' + }); + return lnk; } + var widthRatio = 1.4, + linkScale = d3.scale.linear() + .domain([1, 12]) + .range([widthRatio, 12 * widthRatio]) + .clamp(true); + + function updateLinkWidth (d) { + // TODO: watch out for .showPath/.showTraffic classes + d.el.transition() + .duration(1000) + .attr('stroke-width', linkScale(d.linkWidth)); + } + + function updateLinks() { link = linkG.selectAll('.link') .data(network.links, function (d) { return d.id; }); @@ -572,7 +572,7 @@ }) .transition().duration(1000) .attr({ - 'stroke-width': function (d) { return linkWidth(d.width); }, + 'stroke-width': function (d) { return linkScale(d.linkWidth); }, stroke: '#666' // TODO: remove explicit stroke, rather... }); @@ -589,13 +589,20 @@ //link .foo() .bar() ... // operate on exiting links: - // TODO: figure out how to remove the node 'g' AND its children + // TODO: better transition (longer as a dashed, grey line) link.exit() - .transition() - .duration(750) .attr({ - opacity: 0 + 'stroke-dasharray': '3, 3' }) + .style('opacity', 0.4) + .transition() + .duration(2000) + .attr({ + 'stroke-dasharray': '3, 12' + }) + .transition() + .duration(1000) + .style('opacity', 0.0) .remove(); } @@ -650,7 +657,6 @@ node.y = y || network.view.height() / 2; } - function iconUrl(d) { return 'img/' + d.type + '.png'; } @@ -694,12 +700,48 @@ return (label && label.trim()) ? label : '.'; } + function updateDeviceLabel(d) { + var label = niceLabel(deviceLabel(d)), + node = d.el, + box; + + node.select('text') + .text(label) + .style('opacity', 0) + .transition() + .style('opacity', 1); + + box = adjustRectToFitText(node); + + node.select('rect') + .transition() + .attr(box); + + node.select('image') + .transition() + .attr('x', box.x + config.icons.xoff) + .attr('y', box.y + config.icons.yoff); + } + + function updateHostLabel(d) { + var label = hostLabel(d), + host = d.el; + + host.select('text').text(label); + } + function updateDeviceState(nodeData) { nodeData.el.classed('online', nodeData.online); updateDeviceLabel(nodeData); // TODO: review what else might need to be updated } + function updateLinkState(linkData) { + updateLinkWidth(linkData); + // TODO: review what else might need to be updated + // update label, if showing + } + function updateHostState(hostData) { updateHostLabel(hostData); // TODO: review what else might need to be updated @@ -826,6 +868,25 @@ .remove(); } + function find(id, array) { + for (var idx = 0, n = array.length; idx < n; idx++) { + if (array[idx].id === id) { + return idx; + } + } + return -1; + } + + function removeLinkElement(linkData) { + // remove from lookup cache + delete network.lookup[linkData.id]; + // remove from links array + var idx = find(linkData.id, network.links); + + network.links.splice(linkData.index, 1); + // remove from SVG + updateLinks(); + } function tick() { node.attr({