Adding multi-selection to the GUI and sketching out GUI/Server interactions.
Added persistent meta-data; including node coordinates. Added ability to request path and return one. Change-Id: I3edbdf44bbb8d8133a5e5a1fd0660a3fa5a2d6a1
Showing
9 changed files
with
253 additions
and
9 deletions
| ... | @@ -23,7 +23,9 @@ import org.eclipse.jetty.websocket.WebSocket; | ... | @@ -23,7 +23,9 @@ import org.eclipse.jetty.websocket.WebSocket; |
| 23 | import org.onlab.onos.event.Event; | 23 | import org.onlab.onos.event.Event; |
| 24 | import org.onlab.onos.net.Annotations; | 24 | import org.onlab.onos.net.Annotations; |
| 25 | import org.onlab.onos.net.Device; | 25 | import org.onlab.onos.net.Device; |
| 26 | +import org.onlab.onos.net.DeviceId; | ||
| 26 | import org.onlab.onos.net.Link; | 27 | import org.onlab.onos.net.Link; |
| 28 | +import org.onlab.onos.net.Path; | ||
| 27 | import org.onlab.onos.net.device.DeviceEvent; | 29 | import org.onlab.onos.net.device.DeviceEvent; |
| 28 | import org.onlab.onos.net.device.DeviceService; | 30 | import org.onlab.onos.net.device.DeviceService; |
| 29 | import org.onlab.onos.net.link.LinkEvent; | 31 | import org.onlab.onos.net.link.LinkEvent; |
| ... | @@ -37,7 +39,11 @@ import org.onlab.onos.net.topology.TopologyVertex; | ... | @@ -37,7 +39,11 @@ import org.onlab.onos.net.topology.TopologyVertex; |
| 37 | import org.onlab.osgi.ServiceDirectory; | 39 | import org.onlab.osgi.ServiceDirectory; |
| 38 | 40 | ||
| 39 | import java.io.IOException; | 41 | import java.io.IOException; |
| 42 | +import java.util.HashMap; | ||
| 43 | +import java.util.Map; | ||
| 44 | +import java.util.Set; | ||
| 40 | 45 | ||
| 46 | +import static org.onlab.onos.net.DeviceId.deviceId; | ||
| 41 | import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; | 47 | import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED; |
| 42 | import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED; | 48 | import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED; |
| 43 | import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED; | 49 | import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED; |
| ... | @@ -56,6 +62,12 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe | ... | @@ -56,6 +62,12 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe |
| 56 | 62 | ||
| 57 | private Connection connection; | 63 | private Connection connection; |
| 58 | 64 | ||
| 65 | + // TODO: extract into an external & durable state; good enough for now and demo | ||
| 66 | + private static Map<String, ObjectNode> metaUi = new HashMap<>(); | ||
| 67 | + | ||
| 68 | + private static final String COMPACT = "%s/%s-%s/%s"; | ||
| 69 | + | ||
| 70 | + | ||
| 59 | /** | 71 | /** |
| 60 | * Creates a new web-socket for serving data to GUI topology view. | 72 | * Creates a new web-socket for serving data to GUI topology view. |
| 61 | * | 73 | * |
| ... | @@ -101,9 +113,56 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe | ... | @@ -101,9 +113,56 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe |
| 101 | 113 | ||
| 102 | @Override | 114 | @Override |
| 103 | public void onMessage(String data) { | 115 | public void onMessage(String data) { |
| 104 | - System.out.println("Received: " + data); | 116 | + try { |
| 117 | + ObjectNode event = (ObjectNode) mapper.reader().readTree(data); | ||
| 118 | + String type = event.path("event").asText("unknown"); | ||
| 119 | + ObjectNode payload = (ObjectNode) event.path("payload"); | ||
| 120 | + | ||
| 121 | + switch (type) { | ||
| 122 | + case "updateMeta": | ||
| 123 | + metaUi.put(payload.path("id").asText(), payload); | ||
| 124 | + break; | ||
| 125 | + case "requestPath": | ||
| 126 | + findPath(deviceId(payload.path("one").asText()), | ||
| 127 | + deviceId(payload.path("two").asText())); | ||
| 128 | + default: | ||
| 129 | + break; | ||
| 130 | + } | ||
| 131 | + } catch (IOException e) { | ||
| 132 | + System.out.println("Received: " + data); | ||
| 133 | + } | ||
| 105 | } | 134 | } |
| 106 | 135 | ||
| 136 | + private void findPath(DeviceId one, DeviceId two) { | ||
| 137 | + Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(), | ||
| 138 | + one, two); | ||
| 139 | + if (!paths.isEmpty()) { | ||
| 140 | + ObjectNode payload = mapper.createObjectNode(); | ||
| 141 | + ArrayNode links = mapper.createArrayNode(); | ||
| 142 | + | ||
| 143 | + Path path = paths.iterator().next(); | ||
| 144 | + for (Link link : path.links()) { | ||
| 145 | + links.add(compactLinkString(link)); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + payload.set("links", links); | ||
| 149 | + sendMessage(envelope("showPath", payload)); | ||
| 150 | + } | ||
| 151 | + // TODO: when no path, send a message to the client | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + /** | ||
| 155 | + * Returns a compact string representing the given link. | ||
| 156 | + * | ||
| 157 | + * @param link infrastructure link | ||
| 158 | + * @return formatted link string | ||
| 159 | + */ | ||
| 160 | + public static String compactLinkString(Link link) { | ||
| 161 | + return String.format(COMPACT, link.src().deviceId(), link.src().port(), | ||
| 162 | + link.dst().deviceId(), link.dst().port()); | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + | ||
| 107 | private void sendMessage(String data) { | 166 | private void sendMessage(String data) { |
| 108 | try { | 167 | try { |
| 109 | connection.sendMessage(data); | 168 | connection.sendMessage(data); |
| ... | @@ -130,7 +189,11 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe | ... | @@ -130,7 +189,11 @@ public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListe |
| 130 | // Add labels, props and stuff the payload into envelope. | 189 | // Add labels, props and stuff the payload into envelope. |
| 131 | payload.set("labels", labels); | 190 | payload.set("labels", labels); |
| 132 | payload.set("props", props(device.annotations())); | 191 | payload.set("props", props(device.annotations())); |
| 133 | - payload.set("metaUi", mapper.createObjectNode()); | 192 | + |
| 193 | + ObjectNode meta = metaUi.get(device.id().toString()); | ||
| 194 | + if (meta != null) { | ||
| 195 | + payload.set("metaUi", meta); | ||
| 196 | + } | ||
| 134 | 197 | ||
| 135 | String type = (event.type() == DEVICE_ADDED) ? "addDevice" : | 198 | String type = (event.type() == DEVICE_ADDED) ? "addDevice" : |
| 136 | ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice"); | 199 | ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice"); | ... | ... |
| ... | @@ -28,7 +28,7 @@ | ... | @@ -28,7 +28,7 @@ |
| 28 | 28 | ||
| 29 | // configuration data | 29 | // configuration data |
| 30 | var config = { | 30 | var config = { |
| 31 | - useLiveData: false, | 31 | + useLiveData: true, |
| 32 | debugOn: false, | 32 | debugOn: false, |
| 33 | debug: { | 33 | debug: { |
| 34 | showNodeXY: true, | 34 | showNodeXY: true, |
| ... | @@ -120,7 +120,9 @@ | ... | @@ -120,7 +120,9 @@ |
| 120 | B: toggleBg, | 120 | B: toggleBg, |
| 121 | L: cycleLabels, | 121 | L: cycleLabels, |
| 122 | P: togglePorts, | 122 | P: togglePorts, |
| 123 | - U: unpin | 123 | + U: unpin, |
| 124 | + | ||
| 125 | + X: requestPath | ||
| 124 | }; | 126 | }; |
| 125 | 127 | ||
| 126 | // state variables | 128 | // state variables |
| ... | @@ -132,7 +134,11 @@ | ... | @@ -132,7 +134,11 @@ |
| 132 | }, | 134 | }, |
| 133 | webSock, | 135 | webSock, |
| 134 | labelIdx = 0, | 136 | labelIdx = 0, |
| 135 | - selected = {}, | 137 | + |
| 138 | + //selected = {}, | ||
| 139 | + selectOrder = [], | ||
| 140 | + selections = {}, | ||
| 141 | + | ||
| 136 | highlighted = null, | 142 | highlighted = null, |
| 137 | hovered = null, | 143 | hovered = null, |
| 138 | viewMode = 'showAll', | 144 | viewMode = 'showAll', |
| ... | @@ -239,6 +245,14 @@ | ... | @@ -239,6 +245,14 @@ |
| 239 | view.alert('unpin() callback') | 245 | view.alert('unpin() callback') |
| 240 | } | 246 | } |
| 241 | 247 | ||
| 248 | + function requestPath(view) { | ||
| 249 | + var payload = { | ||
| 250 | + one: selections[selectOrder[0]].obj.id, | ||
| 251 | + two: selections[selectOrder[1]].obj.id | ||
| 252 | + } | ||
| 253 | + sendMessage('requestPath', payload); | ||
| 254 | + } | ||
| 255 | + | ||
| 242 | // ============================== | 256 | // ============================== |
| 243 | // Radio Button Callbacks | 257 | // Radio Button Callbacks |
| 244 | 258 | ||
| ... | @@ -287,7 +301,8 @@ | ... | @@ -287,7 +301,8 @@ |
| 287 | addDevice: addDevice, | 301 | addDevice: addDevice, |
| 288 | updateDevice: updateDevice, | 302 | updateDevice: updateDevice, |
| 289 | removeDevice: removeDevice, | 303 | removeDevice: removeDevice, |
| 290 | - addLink: addLink | 304 | + addLink: addLink, |
| 305 | + showPath: showPath | ||
| 291 | }; | 306 | }; |
| 292 | 307 | ||
| 293 | function addDevice(data) { | 308 | function addDevice(data) { |
| ... | @@ -326,6 +341,10 @@ | ... | @@ -326,6 +341,10 @@ |
| 326 | } | 341 | } |
| 327 | } | 342 | } |
| 328 | 343 | ||
| 344 | + function showPath(data) { | ||
| 345 | + network.view.alert(data.event + "\n" + data.payload.links.length); | ||
| 346 | + } | ||
| 347 | + | ||
| 329 | // .... | 348 | // .... |
| 330 | 349 | ||
| 331 | function unknownEvent(data) { | 350 | function unknownEvent(data) { |
| ... | @@ -611,7 +630,7 @@ | ... | @@ -611,7 +630,7 @@ |
| 611 | }, | 630 | }, |
| 612 | 631 | ||
| 613 | send : function(text) { | 632 | send : function(text) { |
| 614 | - if (text != null && text.length > 0) { | 633 | + if (text != null) { |
| 615 | webSock._send(text); | 634 | webSock._send(text); |
| 616 | } | 635 | } |
| 617 | }, | 636 | }, |
| ... | @@ -619,11 +638,93 @@ | ... | @@ -619,11 +638,93 @@ |
| 619 | _send : function(message) { | 638 | _send : function(message) { |
| 620 | if (webSock.ws) { | 639 | if (webSock.ws) { |
| 621 | webSock.ws.send(message); | 640 | webSock.ws.send(message); |
| 641 | + } else { | ||
| 642 | + network.view.alert('no web socket open'); | ||
| 622 | } | 643 | } |
| 623 | } | 644 | } |
| 624 | 645 | ||
| 625 | }; | 646 | }; |
| 626 | 647 | ||
| 648 | + var sid = 0; | ||
| 649 | + | ||
| 650 | + function sendMessage(evType, payload) { | ||
| 651 | + var toSend = { | ||
| 652 | + event: evType, | ||
| 653 | + sid: ++sid, | ||
| 654 | + payload: payload | ||
| 655 | + }; | ||
| 656 | + webSock.send(JSON.stringify(toSend)); | ||
| 657 | + } | ||
| 658 | + | ||
| 659 | + | ||
| 660 | + // ============================== | ||
| 661 | + // Selection stuff | ||
| 662 | + | ||
| 663 | + function selectObject(obj, el) { | ||
| 664 | + var n, | ||
| 665 | + meta = d3.event.sourceEvent.metaKey; | ||
| 666 | + | ||
| 667 | + if (el) { | ||
| 668 | + n = d3.select(el); | ||
| 669 | + } else { | ||
| 670 | + node.each(function(d) { | ||
| 671 | + if (d == obj) { | ||
| 672 | + n = d3.select(el = this); | ||
| 673 | + } | ||
| 674 | + }); | ||
| 675 | + } | ||
| 676 | + if (!n) return; | ||
| 677 | + | ||
| 678 | + if (meta && n.classed('selected')) { | ||
| 679 | + deselectObject(obj.id); | ||
| 680 | + //flyinPane(null); | ||
| 681 | + return; | ||
| 682 | + } | ||
| 683 | + | ||
| 684 | + if (!meta) { | ||
| 685 | + deselectAll(); | ||
| 686 | + } | ||
| 687 | + | ||
| 688 | + // TODO: allow for mutli selections | ||
| 689 | + var selected = { | ||
| 690 | + obj : obj, | ||
| 691 | + el : el | ||
| 692 | + }; | ||
| 693 | + | ||
| 694 | + selections[obj.id] = selected; | ||
| 695 | + selectOrder.push(obj.id); | ||
| 696 | + | ||
| 697 | + n.classed('selected', true); | ||
| 698 | + //flyinPane(obj); | ||
| 699 | + } | ||
| 700 | + | ||
| 701 | + function deselectObject(id) { | ||
| 702 | + var obj = selections[id]; | ||
| 703 | + if (obj) { | ||
| 704 | + d3.select(obj.el).classed('selected', false); | ||
| 705 | + selections[id] = null; | ||
| 706 | + // TODO: use splice to remove element | ||
| 707 | + } | ||
| 708 | + //flyinPane(null); | ||
| 709 | + } | ||
| 710 | + | ||
| 711 | + function deselectAll() { | ||
| 712 | + // deselect all nodes in the network... | ||
| 713 | + node.classed('selected', false); | ||
| 714 | + selections = {}; | ||
| 715 | + selectOrder = []; | ||
| 716 | + //flyinPane(null); | ||
| 717 | + } | ||
| 718 | + | ||
| 719 | + | ||
| 720 | + $('#view').on('click', function(e) { | ||
| 721 | + if (!$(e.target).closest('.node').length) { | ||
| 722 | + if (!e.metaKey) { | ||
| 723 | + deselectAll(); | ||
| 724 | + } | ||
| 725 | + } | ||
| 726 | + }); | ||
| 727 | + | ||
| 627 | // ============================== | 728 | // ============================== |
| 628 | // View life-cycle callbacks | 729 | // View life-cycle callbacks |
| 629 | 730 | ||
| ... | @@ -678,7 +779,7 @@ | ... | @@ -678,7 +779,7 @@ |
| 678 | } | 779 | } |
| 679 | 780 | ||
| 680 | function selectCb(d, self) { | 781 | function selectCb(d, self) { |
| 681 | - // TODO: selectObject(d, self); | 782 | + selectObject(d, self); |
| 682 | } | 783 | } |
| 683 | 784 | ||
| 684 | function atDragEnd(d, self) { | 785 | function atDragEnd(d, self) { |
| ... | @@ -686,11 +787,21 @@ | ... | @@ -686,11 +787,21 @@ |
| 686 | // if it is a device (not a host) | 787 | // if it is a device (not a host) |
| 687 | if (d.class === 'device') { | 788 | if (d.class === 'device') { |
| 688 | d.fixed = true; | 789 | d.fixed = true; |
| 689 | - d3.select(self).classed('fixed', true) | 790 | + d3.select(self).classed('fixed', true); |
| 791 | + tellServerCoords(d); | ||
| 690 | // TODO: send new [x,y] back to server, via websocket. | 792 | // TODO: send new [x,y] back to server, via websocket. |
| 691 | } | 793 | } |
| 692 | } | 794 | } |
| 693 | 795 | ||
| 796 | + function tellServerCoords(d) { | ||
| 797 | + sendMessage('updateMeta', { | ||
| 798 | + id: d.id, | ||
| 799 | + 'class': d.class, | ||
| 800 | + x: d.x, | ||
| 801 | + y: d.y | ||
| 802 | + }); | ||
| 803 | + } | ||
| 804 | + | ||
| 694 | // set up the force layout | 805 | // set up the force layout |
| 695 | network.force = d3.layout.force() | 806 | network.force = d3.layout.force() |
| 696 | .size(forceDim) | 807 | .size(forceDim) | ... | ... |
-
Please register or login to post a comment