Thomas Vachuska

ONOS-293 Added summary pane and related keyboard shortcuts; also tweaked key hel…

…p sizes and dropped instances toggle from mast. Fixed ONOS-295 bug.

Change-Id: I694901957451cf88df06e6fca3a8d71de144f68e
...@@ -43,10 +43,10 @@ public class SummaryCommand extends AbstractShellCommand { ...@@ -43,10 +43,10 @@ public class SummaryCommand extends AbstractShellCommand {
43 .put("node", get(ClusterService.class).getLocalNode().ip().toString()) 43 .put("node", get(ClusterService.class).getLocalNode().ip().toString())
44 .put("version", get(CoreService.class).version().toString()) 44 .put("version", get(CoreService.class).version().toString())
45 .put("nodes", get(ClusterService.class).getNodes().size()) 45 .put("nodes", get(ClusterService.class).getNodes().size())
46 - .put("devices", get(DeviceService.class).getDeviceCount()) 46 + .put("devices", topology.deviceCount())
47 - .put("links", get(LinkService.class).getLinkCount()) 47 + .put("links", topology.linkCount())
48 .put("hosts", get(HostService.class).getHostCount()) 48 .put("hosts", get(HostService.class).getHostCount())
49 - .put("clusters", topologyService.getClusters(topology).size()) 49 + .put("clusters", topology.clusterCount())
50 .put("paths", topology.pathCount()) 50 .put("paths", topology.pathCount())
51 .put("flows", get(FlowRuleService.class).getFlowRuleCount()) 51 .put("flows", get(FlowRuleService.class).getFlowRuleCount())
52 .put("intents", get(IntentService.class).getIntentCount())); 52 .put("intents", get(IntentService.class).getIntentCount()));
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 # ----------------------------------------------------------------------------- 4 # -----------------------------------------------------------------------------
5 5
6 #export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-7-openjdk-amd64/} 6 #export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-7-openjdk-amd64/}
7 -export JAVA_OPTS="${JAVA_OPTS:--Xms256M -Xmx2048M}" 7 +export JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx2048m}"
8 8
9 cd /opt/onos 9 cd /opt/onos
10 /opt/onos/apache-karaf-$KARAF_VERSION/bin/karaf "$@" 10 /opt/onos/apache-karaf-$KARAF_VERSION/bin/karaf "$@"
......
...@@ -23,6 +23,7 @@ import org.onlab.onos.cluster.ClusterEvent; ...@@ -23,6 +23,7 @@ import org.onlab.onos.cluster.ClusterEvent;
23 import org.onlab.onos.cluster.ClusterService; 23 import org.onlab.onos.cluster.ClusterService;
24 import org.onlab.onos.cluster.ControllerNode; 24 import org.onlab.onos.cluster.ControllerNode;
25 import org.onlab.onos.cluster.NodeId; 25 import org.onlab.onos.cluster.NodeId;
26 +import org.onlab.onos.core.CoreService;
26 import org.onlab.onos.mastership.MastershipService; 27 import org.onlab.onos.mastership.MastershipService;
27 import org.onlab.onos.net.Annotated; 28 import org.onlab.onos.net.Annotated;
28 import org.onlab.onos.net.Annotations; 29 import org.onlab.onos.net.Annotations;
...@@ -56,6 +57,8 @@ import org.onlab.onos.net.link.LinkService; ...@@ -56,6 +57,8 @@ import org.onlab.onos.net.link.LinkService;
56 import org.onlab.onos.net.provider.ProviderId; 57 import org.onlab.onos.net.provider.ProviderId;
57 import org.onlab.onos.net.statistic.Load; 58 import org.onlab.onos.net.statistic.Load;
58 import org.onlab.onos.net.statistic.StatisticService; 59 import org.onlab.onos.net.statistic.StatisticService;
60 +import org.onlab.onos.net.topology.Topology;
61 +import org.onlab.onos.net.topology.TopologyService;
59 import org.onlab.osgi.ServiceDirectory; 62 import org.onlab.osgi.ServiceDirectory;
60 import org.onlab.packet.IpAddress; 63 import org.onlab.packet.IpAddress;
61 import org.slf4j.Logger; 64 import org.slf4j.Logger;
...@@ -117,8 +120,10 @@ public abstract class TopologyViewMessages { ...@@ -117,8 +120,10 @@ public abstract class TopologyViewMessages {
117 protected final IntentService intentService; 120 protected final IntentService intentService;
118 protected final FlowRuleService flowService; 121 protected final FlowRuleService flowService;
119 protected final StatisticService statService; 122 protected final StatisticService statService;
123 + protected final TopologyService topologyService;
120 124
121 protected final ObjectMapper mapper = new ObjectMapper(); 125 protected final ObjectMapper mapper = new ObjectMapper();
126 + private final String version;
122 127
123 // TODO: extract into an external & durable state; good enough for now and demo 128 // TODO: extract into an external & durable state; good enough for now and demo
124 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>(); 129 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
...@@ -138,6 +143,9 @@ public abstract class TopologyViewMessages { ...@@ -138,6 +143,9 @@ public abstract class TopologyViewMessages {
138 intentService = directory.get(IntentService.class); 143 intentService = directory.get(IntentService.class);
139 flowService = directory.get(FlowRuleService.class); 144 flowService = directory.get(FlowRuleService.class);
140 statService = directory.get(StatisticService.class); 145 statService = directory.get(StatisticService.class);
146 + topologyService = directory.get(TopologyService.class);
147 +
148 + version = directory.get(CoreService.class).version().toString();
141 } 149 }
142 150
143 // Retrieves the payload from the specified event. 151 // Retrieves the payload from the specified event.
...@@ -419,6 +427,22 @@ public abstract class TopologyViewMessages { ...@@ -419,6 +427,22 @@ public abstract class TopologyViewMessages {
419 metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento")); 427 metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
420 } 428 }
421 429
430 + // Returns summary response.
431 + protected ObjectNode summmaryMessage(long sid) {
432 + Topology topology = topologyService.currentTopology();
433 + return envelope("showSummary", sid,
434 + json("ONOS Summary", "node",
435 + new Prop("Devices", format(topology.deviceCount())),
436 + new Prop("Links", format(topology.linkCount())),
437 + new Prop("Hosts", format(hostService.getHostCount())),
438 + new Prop("Topology SCCs", format(topology.clusterCount())),
439 + new Prop("Paths", format(topology.pathCount())),
440 + new Separator(),
441 + new Prop("Intents", format(intentService.getIntentCount())),
442 + new Prop("Flows", format(flowService.getFlowRuleCount())),
443 + new Prop("Version", version.replace(".SNAPSHOT", "*"))));
444 + }
445 +
422 // Returns device details response. 446 // Returns device details response.
423 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) { 447 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
424 Device device = deviceService.getDevice(deviceId); 448 Device device = deviceService.getDevice(deviceId);
...@@ -435,12 +459,12 @@ public abstract class TopologyViewMessages { ...@@ -435,12 +459,12 @@ public abstract class TopologyViewMessages {
435 new Prop("S/W Version", device.swVersion()), 459 new Prop("S/W Version", device.swVersion()),
436 new Prop("Serial Number", device.serialNumber()), 460 new Prop("Serial Number", device.serialNumber()),
437 new Separator(), 461 new Separator(),
462 + new Prop("Master", master(deviceId)),
438 new Prop("Latitude", annot.value("latitude")), 463 new Prop("Latitude", annot.value("latitude")),
439 new Prop("Longitude", annot.value("longitude")), 464 new Prop("Longitude", annot.value("longitude")),
440 - new Prop("Ports", Integer.toString(portCount)),
441 - new Prop("Flows", Integer.toString(flowCount)),
442 new Separator(), 465 new Separator(),
443 - new Prop("Master", master(deviceId)))); 466 + new Prop("Ports", Integer.toString(portCount)),
467 + new Prop("Flows", Integer.toString(flowCount))));
444 } 468 }
445 469
446 protected int getFlowCount(DeviceId deviceId) { 470 protected int getFlowCount(DeviceId deviceId) {
...@@ -641,6 +665,12 @@ public abstract class TopologyViewMessages { ...@@ -641,6 +665,12 @@ public abstract class TopologyViewMessages {
641 return format.format(value) + " " + unit; 665 return format.format(value) + " " + unit;
642 } 666 }
643 667
668 + // Formats the given number into a string.
669 + private String format(Number number) {
670 + DecimalFormat format = new DecimalFormat("#,###");
671 + return format.format(number);
672 + }
673 +
644 private boolean isInfrastructureEgress(Link link) { 674 private boolean isInfrastructureEgress(Link link) {
645 return link.src().elementId() instanceof DeviceId; 675 return link.src().elementId() instanceof DeviceId;
646 } 676 }
......
...@@ -42,7 +42,11 @@ import org.onlab.onos.net.link.LinkListener; ...@@ -42,7 +42,11 @@ import org.onlab.onos.net.link.LinkListener;
42 import org.onlab.osgi.ServiceDirectory; 42 import org.onlab.osgi.ServiceDirectory;
43 43
44 import java.io.IOException; 44 import java.io.IOException;
45 +import java.util.ArrayList;
46 +import java.util.Collections;
47 +import java.util.Comparator;
45 import java.util.HashSet; 48 import java.util.HashSet;
49 +import java.util.List;
46 import java.util.Set; 50 import java.util.Set;
47 import java.util.Timer; 51 import java.util.Timer;
48 import java.util.TimerTask; 52 import java.util.TimerTask;
...@@ -70,8 +74,17 @@ public class TopologyViewWebSocket ...@@ -70,8 +74,17 @@ public class TopologyViewWebSocket
70 74
71 private static final String APP_ID = "org.onlab.onos.gui"; 75 private static final String APP_ID = "org.onlab.onos.gui";
72 76
77 + private static final long SUMMARY_FREQUENCY_SEC = 2000;
73 private static final long TRAFFIC_FREQUENCY_SEC = 1000; 78 private static final long TRAFFIC_FREQUENCY_SEC = 1000;
74 79
80 + private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
81 + new Comparator<ControllerNode>() {
82 + @Override
83 + public int compare(ControllerNode o1, ControllerNode o2) {
84 + return o1.id().toString().compareTo(o2.id().toString());
85 + }
86 + };
87 +
75 private final ApplicationId appId; 88 private final ApplicationId appId;
76 89
77 private Connection connection; 90 private Connection connection;
...@@ -83,10 +96,14 @@ public class TopologyViewWebSocket ...@@ -83,10 +96,14 @@ public class TopologyViewWebSocket
83 private final HostListener hostListener = new InternalHostListener(); 96 private final HostListener hostListener = new InternalHostListener();
84 private final IntentListener intentListener = new InternalIntentListener(); 97 private final IntentListener intentListener = new InternalIntentListener();
85 98
86 - // Intents that are being monitored for the GUI 99 + // Timers and objects being monitored
87 - private ObjectNode monitorRequest; 100 + private final Timer timer = new Timer("topology-view");
88 - private final Timer timer = new Timer("intent-traffic-monitor"); 101 +
89 - private final TimerTask timerTask = new IntentTrafficMonitor(); 102 + private TimerTask trafficTask;
103 + private ObjectNode trafficEvent;
104 +
105 + private TimerTask summaryTask;
106 + private ObjectNode summaryEvent;
90 107
91 private long lastActive = System.currentTimeMillis(); 108 private long lastActive = System.currentTimeMillis();
92 private boolean listenersRemoved = false; 109 private boolean listenersRemoved = false;
...@@ -140,7 +157,6 @@ public class TopologyViewWebSocket ...@@ -140,7 +157,6 @@ public class TopologyViewWebSocket
140 this.connection = connection; 157 this.connection = connection;
141 this.control = (FrameConnection) connection; 158 this.control = (FrameConnection) connection;
142 addListeners(); 159 addListeners();
143 - timer.schedule(timerTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
144 160
145 sendAllInstances(); 161 sendAllInstances();
146 sendAllDevices(); 162 sendAllDevices();
...@@ -181,6 +197,7 @@ public class TopologyViewWebSocket ...@@ -181,6 +197,7 @@ public class TopologyViewWebSocket
181 updateMetaUi(event); 197 updateMetaUi(event);
182 } else if (type.equals("addHostIntent")) { 198 } else if (type.equals("addHostIntent")) {
183 createHostIntent(event); 199 createHostIntent(event);
200 +
184 } else if (type.equals("requestTraffic")) { 201 } else if (type.equals("requestTraffic")) {
185 requestTraffic(event); 202 requestTraffic(event);
186 } else if (type.equals("requestAllTraffic")) { 203 } else if (type.equals("requestAllTraffic")) {
...@@ -189,6 +206,11 @@ public class TopologyViewWebSocket ...@@ -189,6 +206,11 @@ public class TopologyViewWebSocket
189 requestDeviceLinkFlows(event); 206 requestDeviceLinkFlows(event);
190 } else if (type.equals("cancelTraffic")) { 207 } else if (type.equals("cancelTraffic")) {
191 cancelTraffic(event); 208 cancelTraffic(event);
209 +
210 + } else if (type.equals("requestSummary")) {
211 + requestSummary(event);
212 + } else if (type.equals("cancelSummary")) {
213 + cancelSummary(event);
192 } 214 }
193 } 215 }
194 216
...@@ -205,7 +227,9 @@ public class TopologyViewWebSocket ...@@ -205,7 +227,9 @@ public class TopologyViewWebSocket
205 227
206 // Sends all controller nodes to the client as node-added messages. 228 // Sends all controller nodes to the client as node-added messages.
207 private void sendAllInstances() { 229 private void sendAllInstances() {
208 - for (ControllerNode node : clusterService.getNodes()) { 230 + List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes());
231 + Collections.sort(nodes, NODE_COMPARATOR);
232 + for (ControllerNode node : nodes) {
209 sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node))); 233 sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node)));
210 } 234 }
211 } 235 }
...@@ -255,22 +279,37 @@ public class TopologyViewWebSocket ...@@ -255,22 +279,37 @@ public class TopologyViewWebSocket
255 HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two, 279 HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
256 DefaultTrafficSelector.builder().build(), 280 DefaultTrafficSelector.builder().build(),
257 DefaultTrafficTreatment.builder().build()); 281 DefaultTrafficTreatment.builder().build());
258 - monitorRequest = event; 282 + trafficEvent = event;
259 intentService.submit(hostIntent); 283 intentService.submit(hostIntent);
260 } 284 }
261 285
286 + private synchronized long startMonitoring(ObjectNode event) {
287 + if (trafficTask == null) {
288 + trafficEvent = event;
289 + trafficTask = new TrafficMonitor();
290 + timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
291 + }
292 + return number(event, "sid");
293 + }
294 +
295 + private synchronized void stopMonitoring() {
296 + if (trafficTask != null) {
297 + trafficTask.cancel();
298 + trafficTask = null;
299 + trafficEvent = null;
300 + }
301 + }
302 +
262 // Subscribes for host traffic messages. 303 // Subscribes for host traffic messages.
263 private synchronized void requestAllTraffic(ObjectNode event) { 304 private synchronized void requestAllTraffic(ObjectNode event) {
264 ObjectNode payload = payload(event); 305 ObjectNode payload = payload(event);
265 - long sid = number(event, "sid"); 306 + long sid = startMonitoring(event);
266 - monitorRequest = event;
267 sendMessage(trafficSummaryMessage(sid)); 307 sendMessage(trafficSummaryMessage(sid));
268 } 308 }
269 309
270 private void requestDeviceLinkFlows(ObjectNode event) { 310 private void requestDeviceLinkFlows(ObjectNode event) {
271 ObjectNode payload = payload(event); 311 ObjectNode payload = payload(event);
272 - long sid = number(event, "sid"); 312 + long sid = startMonitoring(event);
273 - monitorRequest = event;
274 313
275 // Get the set of selected hosts and their intents. 314 // Get the set of selected hosts and their intents.
276 ArrayNode ids = (ArrayNode) payload.path("ids"); 315 ArrayNode ids = (ArrayNode) payload.path("ids");
...@@ -294,8 +333,7 @@ public class TopologyViewWebSocket ...@@ -294,8 +333,7 @@ public class TopologyViewWebSocket
294 return; 333 return;
295 } 334 }
296 335
297 - long sid = number(event, "sid"); 336 + long sid = startMonitoring(event);
298 - monitorRequest = event;
299 337
300 // Get the set of selected hosts and their intents. 338 // Get the set of selected hosts and their intents.
301 ArrayNode ids = (ArrayNode) payload.path("ids"); 339 ArrayNode ids = (ArrayNode) payload.path("ids");
...@@ -325,9 +363,30 @@ public class TopologyViewWebSocket ...@@ -325,9 +363,30 @@ public class TopologyViewWebSocket
325 // Cancels sending traffic messages. 363 // Cancels sending traffic messages.
326 private void cancelTraffic(ObjectNode event) { 364 private void cancelTraffic(ObjectNode event) {
327 sendMessage(trafficMessage(number(event, "sid"))); 365 sendMessage(trafficMessage(number(event, "sid")));
328 - monitorRequest = null; 366 + stopMonitoring();
329 } 367 }
330 368
369 +
370 + // Subscribes for summary messages.
371 + private synchronized void requestSummary(ObjectNode event) {
372 + if (summaryTask == null) {
373 + summaryEvent = event;
374 + summaryTask = new SummaryMonitor();
375 + timer.schedule(summaryTask, SUMMARY_FREQUENCY_SEC, SUMMARY_FREQUENCY_SEC);
376 + }
377 + sendMessage(summmaryMessage(number(event, "sid")));
378 + }
379 +
380 + // Cancels sending summary messages.
381 + private synchronized void cancelSummary(ObjectNode event) {
382 + if (summaryTask != null) {
383 + summaryTask.cancel();
384 + summaryTask = null;
385 + summaryEvent = null;
386 + }
387 + }
388 +
389 +
331 // Adds all internal listeners. 390 // Adds all internal listeners.
332 private void addListeners() { 391 private void addListeners() {
333 clusterService.addListener(clusterListener); 392 clusterService.addListener(clusterListener);
...@@ -385,26 +444,36 @@ public class TopologyViewWebSocket ...@@ -385,26 +444,36 @@ public class TopologyViewWebSocket
385 private class InternalIntentListener implements IntentListener { 444 private class InternalIntentListener implements IntentListener {
386 @Override 445 @Override
387 public void event(IntentEvent event) { 446 public void event(IntentEvent event) {
388 - if (monitorRequest != null) { 447 + if (trafficEvent != null) {
389 - requestTraffic(monitorRequest); 448 + requestTraffic(trafficEvent);
390 } 449 }
391 } 450 }
392 } 451 }
393 452
394 - private class IntentTrafficMonitor extends TimerTask { 453 + private class TrafficMonitor extends TimerTask {
395 @Override 454 @Override
396 public void run() { 455 public void run() {
397 - if (monitorRequest != null) { 456 + if (trafficEvent != null) {
398 - String type = string(monitorRequest, "event", "unknown"); 457 + String type = string(trafficEvent, "event", "unknown");
399 if (type.equals("requestAllTraffic")) { 458 if (type.equals("requestAllTraffic")) {
400 - requestAllTraffic(monitorRequest); 459 + requestAllTraffic(trafficEvent);
401 } else if (type.equals("requestDeviceLinkFlows")) { 460 } else if (type.equals("requestDeviceLinkFlows")) {
402 - requestDeviceLinkFlows(monitorRequest); 461 + requestDeviceLinkFlows(trafficEvent);
403 } else { 462 } else {
404 - requestTraffic(monitorRequest); 463 + requestTraffic(trafficEvent);
405 } 464 }
406 } 465 }
407 } 466 }
408 } 467 }
468 +
469 + private class SummaryMonitor extends TimerTask {
470 + @Override
471 + public void run() {
472 + if (summaryEvent != null) {
473 + requestSummary(summaryEvent);
474 + }
475 + }
476 + }
477 +
409 } 478 }
410 479
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 #feedback svg { 23 #feedback svg {
24 position: absolute; 24 position: absolute;
25 bottom: 0; 25 bottom: 0;
26 - opacity: 0.5; 26 + opacity: 0.8;
27 } 27 }
28 28
29 #feedback svg g.feedbackItem { 29 #feedback svg g.feedbackItem {
...@@ -31,14 +31,11 @@ ...@@ -31,14 +31,11 @@
31 } 31 }
32 32
33 #feedback svg g.feedbackItem rect { 33 #feedback svg g.feedbackItem rect {
34 - fill: #888; 34 + fill: #ccc;
35 - stroke: #666;
36 - stroke-width: 3;
37 - opacity: 0.5
38 } 35 }
39 36
40 #feedback svg g.feedbackItem text { 37 #feedback svg g.feedbackItem text {
41 - fill: #000; 38 + fill: #333;
42 stroke: none; 39 stroke: none;
43 text-anchor: middle; 40 text-anchor: middle;
44 alignment-baseline: middle; 41 alignment-baseline: middle;
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
33 var w = '100%', 33 var w = '100%',
34 h = 200, 34 h = 200,
35 fade = 200, 35 fade = 200,
36 - showFor = 500, 36 + showFor = 1200,
37 vb = '-200 -' + (h/2) + ' 400 ' + h, 37 vb = '-200 -' + (h/2) + ' 400 ' + h,
38 xpad = 20, 38 xpad = 20,
39 ypad = 10; 39 ypad = 10;
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
24 position: absolute; 24 position: absolute;
25 z-index: 100; 25 z-index: 100;
26 display: block; 26 display: block;
27 - top: 10%; 27 + top: 64px;
28 - width: 280px; 28 + width: 260px;
29 right: -300px; 29 right: -300px;
30 opacity: 0; 30 opacity: 0;
31 background-color: rgba(255,255,255,0.8); 31 background-color: rgba(255,255,255,0.8);
......
...@@ -31,10 +31,10 @@ ...@@ -31,10 +31,10 @@
31 } 31 }
32 32
33 #keymap svg text.title { 33 #keymap svg text.title {
34 - font-size: 12pt; 34 + font-size: 10pt;
35 font-style: italic; 35 font-style: italic;
36 text-anchor: middle; 36 text-anchor: middle;
37 - fill: #444; 37 + fill: #999;
38 } 38 }
39 39
40 #keymap svg g.keyItem { 40 #keymap svg g.keyItem {
...@@ -47,17 +47,17 @@ ...@@ -47,17 +47,17 @@
47 } 47 }
48 48
49 #keymap svg text { 49 #keymap svg text {
50 - font-size: 10pt; 50 + font-size: 7pt;
51 alignment-baseline: middle; 51 alignment-baseline: middle;
52 } 52 }
53 53
54 #keymap svg text.key { 54 #keymap svg text.key {
55 - font-size: 10pt; 55 + font-size: 7pt;
56 - fill: #8aa; 56 + fill: #add;
57 } 57 }
58 58
59 #keymap svg text.desc { 59 #keymap svg text.desc {
60 - font-size: 10pt; 60 + font-size: 7pt;
61 - fill: #888; 61 + fill: #aaa;
62 } 62 }
63 63
......
...@@ -35,9 +35,9 @@ ...@@ -35,9 +35,9 @@
35 fade = 500, 35 fade = 500,
36 vb = '-220 -220 440 440', 36 vb = '-220 -220 440 440',
37 paneW = 400, 37 paneW = 400,
38 - paneH = 340, 38 + paneH = 280,
39 offy = 65, 39 offy = 65,
40 - dy = 20, 40 + dy = 14,
41 offKey = 40, 41 offKey = 40,
42 offDesc = offKey + 50, 42 offDesc = offKey + 50,
43 lineW = paneW - (2*offKey); 43 lineW = paneW - (2*offKey);
......
...@@ -763,7 +763,8 @@ ...@@ -763,7 +763,8 @@
763 var pos = position || 'TR', 763 var pos = position || 'TR',
764 cfg = fpConfig[pos], 764 cfg = fpConfig[pos],
765 el, 765 el,
766 - fp; 766 + fp,
767 + on = false;
767 768
768 if (fpanels[id]) { 769 if (fpanels[id]) {
769 buildError('Float panel with id "' + id + '" already exists.'); 770 buildError('Float panel with id "' + id + '" already exists.');
...@@ -792,15 +793,20 @@ ...@@ -792,15 +793,20 @@
792 id: id, 793 id: id,
793 el: el, 794 el: el,
794 pos: pos, 795 pos: pos,
796 + isVisible: function () {
797 + return on;
798 + },
795 799
796 show: function () { 800 show: function () {
797 console.log('show pane: ' + id); 801 console.log('show pane: ' + id);
802 + on = true;
798 el.transition().duration(750) 803 el.transition().duration(750)
799 .style(cfg.side, pxShow()) 804 .style(cfg.side, pxShow())
800 .style('opacity', 1); 805 .style('opacity', 1);
801 }, 806 },
802 hide: function () { 807 hide: function () {
803 console.log('hide pane: ' + id); 808 console.log('hide pane: ' + id);
809 + on = false;
804 el.transition().duration(750) 810 el.transition().duration(750)
805 .style(cfg.side, pxHide()) 811 .style(cfg.side, pxHide())
806 .style('opacity', 0); 812 .style('opacity', 0);
......
...@@ -177,10 +177,66 @@ svg .node.host circle { ...@@ -177,10 +177,66 @@ svg .node.host circle {
177 font-size: 9pt; 177 font-size: 9pt;
178 } 178 }
179 179
180 +/* Fly-in summary pane */
181 +
182 +#topo-summary {
183 + /* gets base CSS from .fpanel in floatPanel.css */
184 + top: 64px;
185 +}
186 +
187 +#topo-summary svg {
188 + display: inline-block;
189 + width: 42px;
190 + height: 42px;
191 +}
192 +
193 +#topo-summary svg .glyphIcon {
194 + fill: black;
195 + stroke: none;
196 + fill-rule: evenodd;
197 +}
198 +
199 +#topo-summary h2 {
200 + position: absolute;
201 + margin: 0px 4px;
202 + top: 20px;
203 + left: 50px;
204 + color: black;
205 +}
206 +
207 +#topo-summary h3 {
208 + margin: 0px 4px;
209 + top: 20px;
210 + left: 50px;
211 + color: black;
212 +}
213 +
214 +#topo-summary p, table {
215 + margin: 4px 4px;
216 +}
217 +
218 +#topo-summary td.label {
219 + font-style: italic;
220 + color: #777;
221 + padding-right: 12px;
222 +}
223 +
224 +#topo-summary td.value {
225 +}
226 +
227 +#topo-summary hr {
228 + height: 1px;
229 + color: #ccc;
230 + background-color: #ccc;
231 + border: 0;
232 +}
233 +
180 /* Fly-in details pane */ 234 /* Fly-in details pane */
181 235
182 #topo-detail { 236 #topo-detail {
183 -/* gets base CSS from .fpanel in floatPanel.css */ 237 + /* gets base CSS from .fpanel in floatPanel.css */
238 + top: 320px;
239 +
184 } 240 }
185 241
186 #topo-detail svg { 242 #topo-detail svg {
......
...@@ -141,6 +141,8 @@ ...@@ -141,6 +141,8 @@
141 S: injectStartupEvents, 141 S: injectStartupEvents,
142 space: injectTestEvent, 142 space: injectTestEvent,
143 143
144 + O: [toggleSummary, 'Toggle ONOS summary pane'],
145 + I: [toggleInstances, 'Toggle ONOS instances pane'],
144 B: [toggleBg, 'Toggle background image'], 146 B: [toggleBg, 'Toggle background image'],
145 L: [cycleLabels, 'Cycle Device labels'], 147 L: [cycleLabels, 'Cycle Device labels'],
146 P: togglePorts, 148 P: togglePorts,
...@@ -182,6 +184,7 @@ ...@@ -182,6 +184,7 @@
182 selections = {}, 184 selections = {},
183 selectOrder = [], 185 selectOrder = [],
184 hovered = null, 186 hovered = null,
187 + summaryPane,
185 detailPane, 188 detailPane,
186 antTimer = null, 189 antTimer = null,
187 onosInstances = {}, 190 onosInstances = {},
...@@ -329,7 +332,7 @@ ...@@ -329,7 +332,7 @@
329 if (hoverMode === hoverModes.length) { 332 if (hoverMode === hoverModes.length) {
330 hoverMode = 0; 333 hoverMode = 0;
331 } 334 }
332 - view.flash('Hover Mode: ' + hoverModes[hoverMode]); 335 + view.flash('Mode: ' + hoverModes[hoverMode]);
333 } 336 }
334 337
335 function togglePorts(view) { 338 function togglePorts(view) {
...@@ -347,8 +350,12 @@ ...@@ -347,8 +350,12 @@
347 function handleEscape(view) { 350 function handleEscape(view) {
348 if (oiShowMaster) { 351 if (oiShowMaster) {
349 cancelAffinity(); 352 cancelAffinity();
350 - } else { 353 + } else if (detailPane.isVisible()) {
351 deselectAll(); 354 deselectAll();
355 + } else if (oiBox.isVisible()) {
356 + oiBox.hide();
357 + } else if (summaryPane.isVisible()) {
358 + cancelSummary();
352 } 359 }
353 } 360 }
354 361
...@@ -585,6 +592,7 @@ ...@@ -585,6 +592,7 @@
585 removeHost: removeHost, 592 removeHost: removeHost,
586 593
587 showDetails: showDetails, 594 showDetails: showDetails,
595 + showSummary: showSummary,
588 showTraffic: showTraffic 596 showTraffic: showTraffic
589 }; 597 };
590 598
...@@ -737,6 +745,12 @@ ...@@ -737,6 +745,12 @@
737 } 745 }
738 } 746 }
739 747
748 + function showSummary(data) {
749 + evTrace(data);
750 + populateSummary(data.payload);
751 + summaryPane.show();
752 + }
753 +
740 function showDetails(data) { 754 function showDetails(data) {
741 evTrace(data); 755 evTrace(data);
742 populateDetails(data.payload); 756 populateDetails(data.payload);
...@@ -824,6 +838,33 @@ ...@@ -824,6 +838,33 @@
824 return true; 838 return true;
825 } 839 }
826 840
841 +
842 + function toggleInstances() {
843 + if (!oiBox.isVisible()) {
844 + oiBox.show();
845 + } else {
846 + oiBox.hide();
847 + }
848 + }
849 +
850 + function toggleSummary() {
851 + if (!summaryPane.isVisible()) {
852 + requestSummary();
853 + } else {
854 + cancelSummary();
855 + }
856 + }
857 +
858 + // request overall summary data
859 + function requestSummary() {
860 + sendMessage('requestSummary', {});
861 + }
862 +
863 + function cancelSummary() {
864 + sendMessage('cancelSummary', {});
865 + summaryPane.hide();
866 + }
867 +
827 // request details for the selected element 868 // request details for the selected element
828 // invoked from selection of a single node. 869 // invoked from selection of a single node.
829 function requestDetails() { 870 function requestDetails() {
...@@ -845,16 +886,20 @@ ...@@ -845,16 +886,20 @@
845 } 886 }
846 887
847 function showTrafficAction() { 888 function showTrafficAction() {
848 - // force intents hover mode 889 + cancelTraffic();
849 hoverMode = 1; 890 hoverMode = 1;
850 showSelectTraffic(); 891 showSelectTraffic();
851 network.view.flash('Related Traffic'); 892 network.view.flash('Related Traffic');
852 } 893 }
853 894
895 + function cancelTraffic() {
896 + sendMessage('cancelTraffic', {});
897 + }
898 +
854 function showSelectTraffic() { 899 function showSelectTraffic() {
855 // if nothing is hovered over, and nothing selected, send cancel request 900 // if nothing is hovered over, and nothing selected, send cancel request
856 if (!hovered && nSel() === 0) { 901 if (!hovered && nSel() === 0) {
857 - sendMessage('cancelTraffic', {}); 902 + cancelTraffic();
858 return; 903 return;
859 } 904 }
860 905
...@@ -870,12 +915,13 @@ ...@@ -870,12 +915,13 @@
870 } 915 }
871 916
872 function showAllTrafficAction() { 917 function showAllTrafficAction() {
918 + cancelTraffic();
873 sendMessage('requestAllTraffic', {}); 919 sendMessage('requestAllTraffic', {});
874 network.view.flash('All Traffic'); 920 network.view.flash('All Traffic');
875 } 921 }
876 922
877 function showDeviceLinkFlowsAction() { 923 function showDeviceLinkFlowsAction() {
878 - // force intents hover mode 924 + cancelTraffic();
879 hoverMode = 2; 925 hoverMode = 2;
880 showDeviceLinkFlows(); 926 showDeviceLinkFlows();
881 network.view.flash('Device Flows'); 927 network.view.flash('Device Flows');
...@@ -884,7 +930,7 @@ ...@@ -884,7 +930,7 @@
884 function showDeviceLinkFlows() { 930 function showDeviceLinkFlows() {
885 // if nothing is hovered over, and nothing selected, send cancel request 931 // if nothing is hovered over, and nothing selected, send cancel request
886 if (!hovered && nSel() === 0) { 932 if (!hovered && nSel() === 0) {
887 - sendMessage('cancelTraffic', {}); 933 + cancelTraffic();
888 return; 934 return;
889 } 935 }
890 var hoverId = (flowsHover() && hovered && hovered.class === 'device') ? 936 var hoverId = (flowsHover() && hovered && hovered.class === 'device') ?
...@@ -907,7 +953,6 @@ ...@@ -907,7 +953,6 @@
907 'xlink:href': iid, 953 'xlink:href': iid,
908 width: dim, 954 width: dim,
909 height: dim 955 height: dim
910 -
911 }); 956 });
912 } 957 }
913 958
...@@ -940,6 +985,15 @@ ...@@ -940,6 +985,15 @@
940 }); 985 });
941 var dim = 30; 986 var dim = 30;
942 appendGlyph(svg, 2, 2, 30, '#node'); 987 appendGlyph(svg, 2, 2, 30, '#node');
988 + svg.append('use')
989 + .attr({
990 + class: 'birdBadge',
991 + transform: translate(8,10),
992 + 'xlink:href': '#bird',
993 + width: 18,
994 + height: 18,
995 + fill: '#fff'
996 + });
943 997
944 $('<div>').attr('class', 'onosTitle').text(d.id).appendTo(el); 998 $('<div>').attr('class', 'onosTitle').text(d.id).appendTo(el);
945 999
...@@ -1720,6 +1774,8 @@ ...@@ -1720,6 +1774,8 @@
1720 1774
1721 webSock.ws.onopen = function() { 1775 webSock.ws.onopen = function() {
1722 noWebSock(false); 1776 noWebSock(false);
1777 + requestSummary();
1778 + oiBox.show();
1723 }; 1779 };
1724 1780
1725 webSock.ws.onmessage = function(m) { 1781 webSock.ws.onmessage = function(m) {
...@@ -1881,12 +1937,17 @@ ...@@ -1881,12 +1937,17 @@
1881 updateDetailPane(); 1937 updateDetailPane();
1882 } 1938 }
1883 1939
1940 + // update the state of the sumary pane
1941 + function updateSummaryPane() {
1942 +
1943 + }
1944 +
1884 // update the state of the detail pane, based on current selections 1945 // update the state of the detail pane, based on current selections
1885 function updateDetailPane() { 1946 function updateDetailPane() {
1886 var nSel = selectOrder.length; 1947 var nSel = selectOrder.length;
1887 if (!nSel) { 1948 if (!nSel) {
1888 detailPane.hide(); 1949 detailPane.hide();
1889 - showTrafficAction(); // sends cancelTraffic event 1950 + cancelTraffic();
1890 } else if (nSel === 1) { 1951 } else if (nSel === 1) {
1891 singleSelect(); 1952 singleSelect();
1892 } else { 1953 } else {
...@@ -1936,6 +1997,40 @@ ...@@ -1936,6 +1997,40 @@
1936 addMultiSelectActions(); 1997 addMultiSelectActions();
1937 } 1998 }
1938 1999
2000 + // TODO: refactor to consolidate with populateDetails
2001 + function populateSummary(data) {
2002 + summaryPane.empty();
2003 +
2004 + var svg = summaryPane.append('svg'),
2005 + iid = iconGlyphUrl(data);
2006 +
2007 + var title = summaryPane.append('h2'),
2008 + table = summaryPane.append('table'),
2009 + tbody = table.append('tbody');
2010 +
2011 + appendGlyph(svg, 0, 0, 40, iid);
2012 +
2013 + svg.append('use')
2014 + .attr({
2015 + class: 'birdBadge',
2016 + transform: translate(8,12),
2017 + 'xlink:href': '#bird',
2018 + width: 24,
2019 + height: 24,
2020 + fill: '#fff'
2021 + });
2022 +
2023 + title.text('ONOS Summary');
2024 +
2025 + data.propOrder.forEach(function(p) {
2026 + if (p === '-') {
2027 + addSep(tbody);
2028 + } else {
2029 + addProp(tbody, p, data.props[p]);
2030 + }
2031 + });
2032 + }
2033 +
1939 function populateDetails(data) { 2034 function populateDetails(data) {
1940 detailPane.empty(); 2035 detailPane.empty();
1941 2036
...@@ -2056,7 +2151,7 @@ ...@@ -2056,7 +2151,7 @@
2056 // TODO: toggle button (and other widgets in the masthead) should be provided 2151 // TODO: toggle button (and other widgets in the masthead) should be provided
2057 // by the framework; not generated by the view. 2152 // by the framework; not generated by the view.
2058 2153
2059 - var showInstances; 2154 + //var showInstances;
2060 2155
2061 function addButtonBar(view) { 2156 function addButtonBar(view) {
2062 var bb = d3.select('#mast') 2157 var bb = d3.select('#mast')
...@@ -2069,20 +2164,20 @@ ...@@ -2069,20 +2164,20 @@
2069 .on('click', cb); 2164 .on('click', cb);
2070 } 2165 }
2071 2166
2072 - showInstances = mkTogBtn('Show Instances', toggleInst); 2167 + //showInstances = mkTogBtn('Show Instances', toggleInst);
2073 } 2168 }
2074 2169
2075 - function instShown() { 2170 + //function instShown() {
2076 - return showInstances.classed('active'); 2171 + // return showInstances.classed('active');
2077 - } 2172 + //}
2078 - function toggleInst() { 2173 + //function toggleInst() {
2079 - showInstances.classed('active', !instShown()); 2174 + // showInstances.classed('active', !instShown());
2080 - if (instShown()) { 2175 + // if (instShown()) {
2081 - oiBox.show(); 2176 + // oiBox.show();
2082 - } else { 2177 + // } else {
2083 - oiBox.hide(); 2178 + // oiBox.hide();
2084 - } 2179 + // }
2085 - } 2180 + //}
2086 2181
2087 function panZoom() { 2182 function panZoom() {
2088 return false; 2183 return false;
...@@ -2370,6 +2465,7 @@ ...@@ -2370,6 +2465,7 @@
2370 resize: resize 2465 resize: resize
2371 }); 2466 });
2372 2467
2468 + summaryPane = onos.ui.addFloatingPanel('topo-summary');
2373 detailPane = onos.ui.addFloatingPanel('topo-detail'); 2469 detailPane = onos.ui.addFloatingPanel('topo-detail');
2374 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL'); 2470 oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
2375 2471
......