ONOS-293 Added summary pane and related keyboard shortcuts; also tweaked key help sizes and dropped instances toggle from mast. Fixed ONOS-295 bug.

Change-Id: I694901957451cf88df06e6fca3a8d71de144f68e
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
index 104fa95..9562040 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewWebSocket.java
@@ -42,7 +42,11 @@
 import org.onlab.osgi.ServiceDirectory;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -70,8 +74,17 @@
 
     private static final String APP_ID = "org.onlab.onos.gui";
 
+    private static final long SUMMARY_FREQUENCY_SEC = 2000;
     private static final long TRAFFIC_FREQUENCY_SEC = 1000;
 
+    private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
+            new Comparator<ControllerNode>() {
+                @Override
+                public int compare(ControllerNode o1, ControllerNode o2) {
+                    return o1.id().toString().compareTo(o2.id().toString());
+                }
+            };
+
     private final ApplicationId appId;
 
     private Connection connection;
@@ -83,10 +96,14 @@
     private final HostListener hostListener = new InternalHostListener();
     private final IntentListener intentListener = new InternalIntentListener();
 
-    // Intents that are being monitored for the GUI
-    private ObjectNode monitorRequest;
-    private final Timer timer = new Timer("intent-traffic-monitor");
-    private final TimerTask timerTask = new IntentTrafficMonitor();
+    // Timers and objects being monitored
+    private final Timer timer = new Timer("topology-view");
+
+    private TimerTask trafficTask;
+    private ObjectNode trafficEvent;
+
+    private TimerTask summaryTask;
+    private ObjectNode summaryEvent;
 
     private long lastActive = System.currentTimeMillis();
     private boolean listenersRemoved = false;
@@ -140,7 +157,6 @@
         this.connection = connection;
         this.control = (FrameConnection) connection;
         addListeners();
-        timer.schedule(timerTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
 
         sendAllInstances();
         sendAllDevices();
@@ -181,6 +197,7 @@
             updateMetaUi(event);
         } else if (type.equals("addHostIntent")) {
             createHostIntent(event);
+
         } else if (type.equals("requestTraffic")) {
             requestTraffic(event);
         } else if (type.equals("requestAllTraffic")) {
@@ -189,6 +206,11 @@
             requestDeviceLinkFlows(event);
         } else if (type.equals("cancelTraffic")) {
             cancelTraffic(event);
+
+        } else if (type.equals("requestSummary")) {
+            requestSummary(event);
+        } else if (type.equals("cancelSummary")) {
+            cancelSummary(event);
         }
     }
 
@@ -205,7 +227,9 @@
 
     // Sends all controller nodes to the client as node-added messages.
     private void sendAllInstances() {
-        for (ControllerNode node : clusterService.getNodes()) {
+        List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes());
+        Collections.sort(nodes, NODE_COMPARATOR);
+        for (ControllerNode node : nodes) {
             sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node)));
         }
     }
@@ -255,22 +279,37 @@
         HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
                                                            DefaultTrafficSelector.builder().build(),
                                                            DefaultTrafficTreatment.builder().build());
-        monitorRequest = event;
+        trafficEvent = event;
         intentService.submit(hostIntent);
     }
 
+    private synchronized long startMonitoring(ObjectNode event) {
+        if (trafficTask == null) {
+            trafficEvent = event;
+            trafficTask = new TrafficMonitor();
+            timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
+        }
+        return number(event, "sid");
+    }
+
+    private synchronized void stopMonitoring() {
+        if (trafficTask != null) {
+            trafficTask.cancel();
+            trafficTask = null;
+            trafficEvent = null;
+        }
+    }
+
     // Subscribes for host traffic messages.
     private synchronized void requestAllTraffic(ObjectNode event) {
         ObjectNode payload = payload(event);
-        long sid = number(event, "sid");
-        monitorRequest = event;
+        long sid = startMonitoring(event);
         sendMessage(trafficSummaryMessage(sid));
     }
 
     private void requestDeviceLinkFlows(ObjectNode event) {
         ObjectNode payload = payload(event);
-        long sid = number(event, "sid");
-        monitorRequest = event;
+        long sid = startMonitoring(event);
 
         // Get the set of selected hosts and their intents.
         ArrayNode ids = (ArrayNode) payload.path("ids");
@@ -294,8 +333,7 @@
             return;
         }
 
-        long sid = number(event, "sid");
-        monitorRequest = event;
+        long sid = startMonitoring(event);
 
         // Get the set of selected hosts and their intents.
         ArrayNode ids = (ArrayNode) payload.path("ids");
@@ -325,9 +363,30 @@
     // Cancels sending traffic messages.
     private void cancelTraffic(ObjectNode event) {
         sendMessage(trafficMessage(number(event, "sid")));
-        monitorRequest = null;
+        stopMonitoring();
     }
 
+
+    // Subscribes for summary messages.
+    private synchronized void requestSummary(ObjectNode event) {
+        if (summaryTask == null) {
+            summaryEvent = event;
+            summaryTask = new SummaryMonitor();
+            timer.schedule(summaryTask, SUMMARY_FREQUENCY_SEC, SUMMARY_FREQUENCY_SEC);
+        }
+        sendMessage(summmaryMessage(number(event, "sid")));
+    }
+
+    // Cancels sending summary messages.
+    private synchronized void cancelSummary(ObjectNode event) {
+        if (summaryTask != null) {
+            summaryTask.cancel();
+            summaryTask = null;
+            summaryEvent = null;
+        }
+    }
+
+
     // Adds all internal listeners.
     private void addListeners() {
         clusterService.addListener(clusterListener);
@@ -385,26 +444,36 @@
     private class InternalIntentListener implements IntentListener {
         @Override
         public void event(IntentEvent event) {
-            if (monitorRequest != null) {
-                requestTraffic(monitorRequest);
+            if (trafficEvent != null) {
+                requestTraffic(trafficEvent);
             }
         }
     }
 
-    private class IntentTrafficMonitor extends TimerTask {
+    private class TrafficMonitor extends TimerTask {
         @Override
         public void run() {
-            if (monitorRequest != null) {
-                String type = string(monitorRequest, "event", "unknown");
+            if (trafficEvent != null) {
+                String type = string(trafficEvent, "event", "unknown");
                 if (type.equals("requestAllTraffic")) {
-                    requestAllTraffic(monitorRequest);
+                    requestAllTraffic(trafficEvent);
                 } else if (type.equals("requestDeviceLinkFlows")) {
-                    requestDeviceLinkFlows(monitorRequest);
+                    requestDeviceLinkFlows(trafficEvent);
                 } else {
-                    requestTraffic(monitorRequest);
+                    requestTraffic(trafficEvent);
                 }
             }
         }
     }
+
+    private class SummaryMonitor extends TimerTask {
+        @Override
+        public void run() {
+            if (summaryEvent != null) {
+                requestSummary(summaryEvent);
+            }
+        }
+    }
+
 }