GUI fixes/breaks.

Change-Id: Ic5c8b087cc32506162153b2756a677c7d9e3bdd7
diff --git a/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java b/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
index 44aa9f7..3b1a4a3 100644
--- a/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
+++ b/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
@@ -66,7 +66,7 @@
 public class DefaultTopologyProvider extends AbstractProvider
         implements TopologyProvider {
 
-    private static final int MAX_THREADS = 8;
+    private static final int MAX_THREADS = 32;
     private static final int DEFAULT_MAX_EVENTS = 1000;
     private static final int DEFAULT_MAX_IDLE_MS = 10;
     private static final int DEFAULT_MAX_BATCH_MS = 50;
diff --git a/tools/test/topos/topo-200sw-linkalarm.py b/tools/test/topos/topo-200sw-linkalarm.py
new file mode 100644
index 0000000..bb964e2
--- /dev/null
+++ b/tools/test/topos/topo-200sw-linkalarm.py
@@ -0,0 +1,20 @@
+
+from mininet.topo import Topo
+
+class MyTopo( Topo ):
+        "10 'floating' switch topology"
+
+        def __init__( self ):
+                # Initialize topology
+                Topo.__init__( self )
+
+                sw_list = []
+                swC = self.addSwitch('sc', dpid = 'ffffffff00000001')
+
+                for i in range(1, 201):
+                        switch=self.addSwitch('s'+str(i), dpid = str(i).zfill(16))
+                        self.addLink(switch,swC)
+
+                        sw_list.append(switch)
+
+topos = { 'mytopo': ( lambda: MyTopo() ) }
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewIntentFilter.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewIntentFilter.java
index 2585b84..e5c9249 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewIntentFilter.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewIntentFilter.java
@@ -33,6 +33,7 @@
 import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.onos.net.link.LinkService;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -71,17 +72,19 @@
      * Finds all path (host-to-host or point-to-point) intents that pertains
      * to the given hosts.
      *
-     * @param hosts   set of hosts to query by
-     * @param devices set of devices to query by
+     * @param hosts         set of hosts to query by
+     * @param devices       set of devices to query by
+     * @param sourceIntents collection of intents to search
      * @return set of intents that 'match' all hosts and devices given
      */
-    Set<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) {
+    List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
+                                 Iterable<Intent> sourceIntents) {
         // Derive from this the set of edge connect points.
         Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
 
         // Iterate over all intents and produce a set that contains only those
         // intents that target all selected hosts or derived edge connect points.
-        return getIntents(hosts, devices, edgePoints);
+        return getIntents(hosts, devices, edgePoints, sourceIntents);
     }
 
 
@@ -94,10 +97,11 @@
         return edgePoints;
     }
 
-    // Produces a set of intents that target all selected hosts, devices or connect points.
-    private Set<Intent> getIntents(Set<Host> hosts, Set<Device> devices,
-                                   Set<ConnectPoint> edgePoints) {
-        Set<Intent> intents = new HashSet<>();
+    // Produces a list of intents that target all selected hosts, devices or connect points.
+    private List<Intent> getIntents(Set<Host> hosts, Set<Device> devices,
+                                    Set<ConnectPoint> edgePoints,
+                                    Iterable<Intent> sourceIntents) {
+        List<Intent> intents = new ArrayList<>();
         if (hosts.isEmpty() && devices.isEmpty()) {
             return intents;
         }
@@ -105,7 +109,7 @@
         Set<OpticalConnectivityIntent> opticalIntents = new HashSet<>();
 
         // Search through all intents and see if they are relevant to our search.
-        for (Intent intent : intentService.getIntents()) {
+        for (Intent intent : sourceIntents) {
             if (intentService.getIntentState(intent.id()) == INSTALLED) {
                 boolean isRelevant = false;
                 if (intent instanceof HostToHostIntent) {
@@ -140,7 +144,7 @@
     }
 
     // Indicates whether the specified intent involves all of the given hosts.
-    private boolean isIntentRelevantToHosts(HostToHostIntent intent, Set<Host> hosts) {
+    private boolean isIntentRelevantToHosts(HostToHostIntent intent, Iterable<Host> hosts) {
         for (Host host : hosts) {
             HostId id = host.id();
             // Bail if intent does not involve this host.
@@ -152,7 +156,7 @@
     }
 
     // Indicates whether the specified intent involves all of the given devices.
-    private boolean isIntentRelevantToDevices(Intent intent, Set<Device> devices) {
+    private boolean isIntentRelevantToDevices(Intent intent, Iterable<Device> devices) {
         List<Intent> installables = intentService.getInstallableIntents(intent.id());
         for (Device device : devices) {
             if (!isIntentRelevantToDevice(installables, device)) {
@@ -192,7 +196,8 @@
         return false;
     }
 
-    private boolean isIntentRelevant(PointToPointIntent intent, Set<ConnectPoint> edgePoints) {
+    private boolean isIntentRelevant(PointToPointIntent intent,
+                                     Iterable<ConnectPoint> edgePoints) {
         for (ConnectPoint point : edgePoints) {
             // Bail if intent does not involve this edge point.
             if (!point.equals(intent.egressPoint()) &&
@@ -205,7 +210,7 @@
 
     // Indicates whether the specified intent involves all of the given edge points.
     private boolean isIntentRelevant(MultiPointToSinglePointIntent intent,
-                                     Set<ConnectPoint> edgePoints) {
+                                     Iterable<ConnectPoint> edgePoints) {
         for (ConnectPoint point : edgePoints) {
             // Bail if intent does not involve this edge point.
             if (!point.equals(intent.egressPoint()) &&
@@ -218,7 +223,7 @@
 
     // Indicates whether the specified intent involves all of the given edge points.
     private boolean isIntentRelevant(OpticalConnectivityIntent opticalIntent,
-                                     Set<Intent> intents) {
+                                     Iterable<Intent> intents) {
         Link ccSrc = getFirstLink(opticalIntent.getSrc(), false);
         Link ccDst = getFirstLink(opticalIntent.getDst(), true);
 
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
index c49fbee..486e375 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyViewMessages.java
@@ -676,13 +676,16 @@
                 List<Intent> installables = intentService.getInstallableIntents(intent.id());
                 if (installables != null) {
                     for (Intent installable : installables) {
-                        String cls = isOptical ? trafficClass.type + " optical" : trafficClass.type;
+                        String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
                         if (installable instanceof PathIntent) {
-                            classifyLinks(cls, biLinks, ((PathIntent) installable).path().links());
+                            classifyLinks(type, biLinks, trafficClass.showTraffic,
+                                          ((PathIntent) installable).path().links());
                         } else if (installable instanceof LinkCollectionIntent) {
-                            classifyLinks(cls, biLinks, ((LinkCollectionIntent) installable).links());
+                            classifyLinks(type, biLinks, trafficClass.showTraffic,
+                                          ((LinkCollectionIntent) installable).links());
                         } else if (installable instanceof OpticalPathIntent) {
-                            classifyLinks(cls, biLinks, ((OpticalPathIntent) installable).path().links());
+                            classifyLinks(type, biLinks, trafficClass.showTraffic,
+                                    ((OpticalPathIntent) installable).path().links());
                         }
                     }
                 }
@@ -695,12 +698,14 @@
     // Adds the link segments (path or tree) associated with the specified
     // connectivity intent
     private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
-                               Iterable<Link> links) {
+                               boolean showTraffic, Iterable<Link> links) {
         if (links != null) {
             for (Link link : links) {
                 BiLink biLink = addLink(biLinks, link);
                 if (isInfrastructureEgress(link)) {
-                    biLink.addLoad(statService.load(link));
+                    if (showTraffic) {
+                        biLink.addLoad(statService.load(link));
+                    }
                     biLink.addClass(type);
                 }
             }
@@ -862,12 +867,18 @@
 
     // Auxiliary carrier of data for requesting traffic message.
     protected class TrafficClass {
+        public final boolean showTraffic;
         public final String type;
-        public final Set<Intent> intents;
+        public final Iterable<Intent> intents;
 
-        TrafficClass(String type, Set<Intent> intents) {
+        TrafficClass(String type, Iterable<Intent> intents) {
+            this(type, intents, false);
+        }
+
+        TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
             this.type = type;
             this.intents = intents;
+            this.showTraffic = showTraffic;
         }
     }
 
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 9e46da3..bd02065 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
@@ -90,7 +90,8 @@
 
     private static final String APP_ID = "org.onlab.onos.gui";
 
-    private static final long TRAFFIC_FREQUENCY_SEC = 2000;
+    private static final long TRAFFIC_FREQUENCY = 2000;
+    private static final long SUMMARY_FREQUENCY = 30000;
 
     private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
             new Comparator<ControllerNode>() {
@@ -103,9 +104,9 @@
 
     private final Timer timer = new Timer("topology-view");
 
-    private static final int MAX_EVENTS = 500;
-    private static final int MAX_BATCH_MS = 1000;
-    private static final int MAX_IDLE_MS = 500;
+    private static final int MAX_EVENTS = 1000;
+    private static final int MAX_BATCH_MS = 5000;
+    private static final int MAX_IDLE_MS = 1000;
 
     private final ApplicationId appId;
 
@@ -122,15 +123,23 @@
 
     private final EventAccumulator eventAccummulator = new InternalEventAccummulator();
 
-    private boolean summaryEnabled = true;
     private TimerTask trafficTask;
     private ObjectNode trafficEvent;
 
+    private TimerTask summaryTask;
+    private ObjectNode summaryEvent;
+
     private long lastActive = System.currentTimeMillis();
     private boolean listenersRemoved = false;
 
     private TopologyViewIntentFilter intentFilter;
 
+    // Current selection context
+    private Set<Host> selectedHosts;
+    private Set<Device> selectedDevices;
+    private List<Intent> selectedIntents;
+    private int currentIntentIndex = -1;
+
     /**
      * Creates a new web-socket for serving data to GUI topology view.
      *
@@ -204,7 +213,6 @@
             processMessage((ObjectNode) mapper.reader().readTree(data));
         } catch (Exception e) {
             log.warn("Unable to parse GUI request {} due to {}", data, e);
-            log.warn("Boom!!!!", e);
         }
     }
 
@@ -221,19 +229,29 @@
         } else if (type.equals("addMultiSourceIntent")) {
             createMultiSourceIntent(event);
 
-        } else if (type.equals("requestTraffic")) {
-            requestTraffic(event);
+        } else if (type.equals("requestRelatedIntents")) {
+            requestRelatedIntents(event);
+        } else if (type.equals("requestNextRelatedIntent")) {
+            requestNextRelatedIntent(event);
+        } else if (type.equals("requestSelectedIntentTraffic")) {
+            requestSelectedIntentTraffic(event);
+
         } else if (type.equals("requestAllTraffic")) {
             requestAllTraffic(event);
+            startTrafficMonitoring(event);
+
         } else if (type.equals("requestDeviceLinkFlows")) {
             requestDeviceLinkFlows(event);
+            startTrafficMonitoring(event);
+
         } else if (type.equals("cancelTraffic")) {
             cancelTraffic(event);
 
         } else if (type.equals("requestSummary")) {
             requestSummary(event);
+            startSummaryMonitoring(event);
         } else if (type.equals("cancelSummary")) {
-            cancelSummary(event);
+            stopSummaryMonitoring();
 
         } else if (type.equals("equalizeMasters")) {
             equalizeMasters(event);
@@ -324,8 +342,9 @@
                 new HostToHostIntent(appId, one, two,
                                      DefaultTrafficSelector.builder().build(),
                                      DefaultTrafficTreatment.builder().build());
-        startMonitoring(event);
+
         intentService.submit(intent);
+        startMonitoringIntent(event, intent);
     }
 
     // Creates multi-source-to-single-dest intent.
@@ -348,10 +367,24 @@
         MultiPointToSinglePointIntent intent =
                 new MultiPointToSinglePointIntent(appId, selector, treatment,
                                                   ingressPoints, dstHost.location());
-        startMonitoring(event);
+
         intentService.submit(intent);
+        startMonitoringIntent(event, intent);
     }
 
+
+    private synchronized void startMonitoringIntent(ObjectNode event, Intent intent) {
+        selectedHosts = new HashSet<>();
+        selectedDevices = new HashSet<>();
+        selectedIntents = new ArrayList<>();
+        selectedIntents.add(intent);
+        currentIntentIndex = -1;
+        requestNextRelatedIntent(event);
+        requestSelectedIntentTraffic(event);
+    }
+
+
+
     private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
         Set<ConnectPoint> points = new HashSet<>();
         for (HostId hostId : hostIds) {
@@ -374,17 +407,15 @@
     }
 
 
-    private synchronized long startMonitoring(ObjectNode event) {
-        if (trafficTask != null) {
-            stopMonitoring();
-        }
+    private synchronized long startTrafficMonitoring(ObjectNode event) {
+        stopTrafficMonitoring();
         trafficEvent = event;
         trafficTask = new TrafficMonitor();
-        timer.schedule(trafficTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
+        timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY);
         return number(event, "sid");
     }
 
-    private synchronized void stopMonitoring() {
+    private synchronized void stopTrafficMonitoring() {
         if (trafficTask != null) {
             trafficTask.cancel();
             trafficTask = null;
@@ -394,13 +425,13 @@
 
     // Subscribes for host traffic messages.
     private synchronized void requestAllTraffic(ObjectNode event) {
-        long sid = startMonitoring(event);
+        long sid = startTrafficMonitoring(event);
         sendMessage(trafficSummaryMessage(sid));
     }
 
     private void requestDeviceLinkFlows(ObjectNode event) {
         ObjectNode payload = payload(event);
-        long sid = startMonitoring(event);
+        long sid = startTrafficMonitoring(event);
 
         // Get the set of selected hosts and their intents.
         ArrayNode ids = (ArrayNode) payload.path("ids");
@@ -416,58 +447,122 @@
     }
 
 
-    // Subscribes for host traffic messages.
-    private synchronized void requestTraffic(ObjectNode event) {
+    // Requests related intents message.
+    private synchronized void requestRelatedIntents(ObjectNode event) {
         ObjectNode payload = payload(event);
         if (!payload.has("ids")) {
             return;
         }
 
-        long sid = startMonitoring(event);
+        long sid = number(event, "sid");
 
-        // Get the set of selected hosts and their intents.
-        ArrayNode ids = (ArrayNode) payload.path("ids");
-        Set<Host> hosts = getHosts(ids);
-        Set<Device> devices = getDevices(ids);
-        Set<Intent> intents = intentFilter.findPathIntents(hosts, devices);
+        // Cancel any other traffic monitoring mode.
+        stopTrafficMonitoring();
 
-        // If there is a hover node, include it in the hosts and find intents.
         String hover = string(payload, "hover");
-        Set<Intent> hoverIntents;
+        if (haveSelectedIntents()) {
+            // Get the set of selected hosts and their intents.
+            ArrayNode ids = (ArrayNode) payload.path("ids");
+            selectedHosts = getHosts(ids);
+            selectedDevices = getDevices(ids);
+            selectedIntents = intentFilter.findPathIntents(selectedHosts, selectedDevices,
+                                                           intentService.getIntents());
+            currentIntentIndex = -1;
+
+            // Send a message to highlight all links of all monitored intents.
+            sendMessage(trafficMessage(sid, new TrafficClass("primary", selectedIntents)));
+        }
+
         if (!isNullOrEmpty(hover)) {
-            addHover(hosts, devices, hover);
-            hoverIntents = intentFilter.findPathIntents(hosts, devices);
-            intents.removeAll(hoverIntents);
+            // If there is a hover node, include it in the selection and find intents.
+            processExtendedSelection(sid, hover);
+        }
+    }
 
-            // Send an initial message to highlight all links of all monitored intents.
-            sendMessage(trafficMessage(sid,
-                                       new TrafficClass("primary", hoverIntents),
-                                       new TrafficClass("secondary", intents)));
+    private boolean haveSelectedIntents() {
+        return selectedIntents != null && !selectedIntents.isEmpty();
+    }
 
-        } else {
-            // Send an initial message to highlight all links of all monitored intents.
-            sendMessage(trafficMessage(sid, new TrafficClass("primary", intents)));
+    private void processExtendedSelection(long sid, String hover) {
+        Set<Host> hoverSelHosts = new HashSet<>(selectedHosts);
+        Set<Device> hoverSelDevices = new HashSet<>(selectedDevices);
+        addHover(hoverSelHosts, hoverSelDevices, hover);
+
+        List<Intent> primary =
+                intentFilter.findPathIntents(hoverSelHosts, hoverSelDevices,
+                                             selectedIntents);
+        Set<Intent> secondary = new HashSet<>(selectedIntents);
+        secondary.removeAll(primary);
+
+        // Send a message to highlight all links of all monitored intents.
+        sendMessage(trafficMessage(sid, new TrafficClass("primary", primary),
+                                   new TrafficClass("secondary", secondary)));
+    }
+
+    // Requests next of the related intents.
+    private void requestNextRelatedIntent(ObjectNode event) {
+        if (haveSelectedIntents()) {
+            currentIntentIndex = (currentIntentIndex + 1) % selectedIntents.size();
+            Intent selectedIntent = selectedIntents.get(currentIntentIndex);
+            log.info("Requested next intent {}", selectedIntent.id());
+
+            Set<Intent> primary = new HashSet<>();
+            primary.add(selectedIntent);
+
+            Set<Intent> secondary = new HashSet<>(selectedIntents);
+            secondary.remove(selectedIntent);
+
+            // Send a message to highlight all links of the selected intent.
+            sendMessage(trafficMessage(number(event, "sid"),
+                                       new TrafficClass("primary", primary),
+                                       new TrafficClass("secondary", secondary)));
+        }
+    }
+
+    // Requests monitoring of traffic for the selected intent.
+    private void requestSelectedIntentTraffic(ObjectNode event) {
+        if (haveSelectedIntents()) {
+            Intent selectedIntent = selectedIntents.get(currentIntentIndex);
+            log.info("Requested traffic for selected {}", selectedIntent.id());
+
+            Set<Intent> primary = new HashSet<>();
+            primary.add(selectedIntent);
+
+            // Send a message to highlight all links of the selected intent.
+            sendMessage(trafficMessage(number(event, "sid"),
+                                       new TrafficClass("primary", primary, true)));
         }
     }
 
     // Cancels sending traffic messages.
     private void cancelTraffic(ObjectNode event) {
+        selectedIntents = null;
         sendMessage(trafficMessage(number(event, "sid")));
-        stopMonitoring();
+        stopTrafficMonitoring();
     }
 
 
+    private synchronized long startSummaryMonitoring(ObjectNode event) {
+        stopSummaryMonitoring();
+        summaryEvent = event;
+        summaryTask = new SummaryMonitor();
+        timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY);
+        return number(event, "sid");
+    }
+
+    private synchronized void stopSummaryMonitoring() {
+        if (summaryEvent != null) {
+            summaryTask.cancel();
+            summaryTask = null;
+            summaryEvent = null;
+        }
+    }
+
     // Subscribes for summary messages.
     private synchronized void requestSummary(ObjectNode event) {
-        summaryEnabled = true;
         sendMessage(summmaryMessage(number(event, "sid")));
     }
 
-    // Cancels sending summary messages.
-    private synchronized void cancelSummary(ObjectNode event) {
-        summaryEnabled = false;
-    }
-
 
     // Forces mastership role rebalancing.
     private void equalizeMasters(ObjectNode event) {
@@ -550,7 +645,7 @@
         @Override
         public void event(IntentEvent event) {
             if (trafficEvent != null) {
-                requestTraffic(trafficEvent);
+                requestSelectedIntentTraffic(trafficEvent);
             }
             eventAccummulator.add(event);
         }
@@ -564,6 +659,7 @@
         }
     }
 
+    // Periodic update of the traffic information
     private class TrafficMonitor extends TimerTask {
         @Override
         public void run() {
@@ -574,8 +670,8 @@
                         requestAllTraffic(trafficEvent);
                     } else if (type.equals("requestDeviceLinkFlows")) {
                         requestDeviceLinkFlows(trafficEvent);
-                    } else {
-                        requestTraffic(trafficEvent);
+                    } else if (type.equals("requestSelectedIntentTraffic")) {
+                        requestSelectedIntentTraffic(trafficEvent);
                     }
                 }
             } catch (Exception e) {
@@ -585,6 +681,20 @@
         }
     }
 
+    // Periodic update of the summary information
+    private class SummaryMonitor extends TimerTask {
+        @Override
+        public void run() {
+            try {
+                if (summaryEvent != null) {
+                    requestSummary(summaryEvent);
+                }
+            } catch (Exception e) {
+                log.warn("Unable to handle summary request due to {}", e.getMessage());
+            }
+        }
+    }
+
     // Accumulates events to drive methodic update of the summary pane.
     private class InternalEventAccummulator extends AbstractEventAccumulator {
         protected InternalEventAccummulator() {
@@ -594,7 +704,7 @@
         @Override
         public void processEvents(List<Event> events) {
             try {
-                if (summaryEnabled) {
+                if (summaryEvent != null) {
                     sendMessage(summmaryMessage(0));
                 }
             } catch (Exception e) {
diff --git a/web/gui/src/main/webapp/topo.js b/web/gui/src/main/webapp/topo.js
index f52b0c9..ea0d731 100644
--- a/web/gui/src/main/webapp/topo.js
+++ b/web/gui/src/main/webapp/topo.js
@@ -145,8 +145,10 @@
         P: togglePorts,
         U: [unpin, 'Unpin node (hover mouse over)'],
         R: [resetPanZoom, 'Reset pan / zoom'],
-        V: [showTrafficAction, 'Show related traffic'],
-        A: [showAllTrafficAction, 'Show all traffic'],
+        V: [showRelatedIntentsAction, 'Show all related intents'],
+        N: [showNextIntentAction, 'Show next related intent'],
+        W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
+        A: [showAllTrafficAction, 'Monitor all traffic'],
         F: [showDeviceLinkFlowsAction, 'Show device link flows'],
         X: [toggleNodeLock, 'Lock / unlock node positions'],
         Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
@@ -209,10 +211,11 @@
         oblique = false;
 
     // constants
-    var hoverModeAll = 1,
+    var hoverModeNone = 0,
+        hoverModeAll = 1,
         hoverModeFlows = 2,
         hoverModeIntents = 3,
-        hoverMode = hoverModeFlows;
+        hoverMode = hoverModeNone;
 
     // D3 selections
     var svg,
@@ -394,7 +397,7 @@
             cancelSummary();
             stopAntTimer();
         } else {
-            hoverMode = hoverModeFlows;
+            hoverMode = hoverModeNone;
         }
     }
 
@@ -1219,22 +1222,20 @@
     }
 
     function requestTrafficForMode() {
-        if (hoverMode === hoverModeAll) {
-            requestAllTraffic();
-        } else if (hoverMode === hoverModeFlows) {
+        if (hoverMode === hoverModeFlows) {
             requestDeviceLinkFlows();
         } else if (hoverMode === hoverModeIntents) {
-            requestSelectTraffic();
+            requestRelatedIntents();
         }
     }
 
-    function showTrafficAction() {
+    function showRelatedIntentsAction() {
         hoverMode = hoverModeIntents;
-        requestSelectTraffic();
-        flash('Related Traffic');
+        requestRelatedIntents();
+        flash('Related intents');
     }
 
-    function requestSelectTraffic() {
+    function requestRelatedIntents() {
         function hoverValid() {
             return hoverMode === hoverModeIntents &&
                 hovered &&
@@ -1242,13 +1243,24 @@
         }
 
         if (validateSelectionContext()) {
-            sendMessage('requestTraffic', {
+            sendMessage('requestRelatedIntents', {
                 ids: selectOrder,
                 hover: hoverValid() ? hovered.id : ''
             });
         }
     }
 
+    function showNextIntentAction() {
+        hoverMode = hoverModeNone;
+        sendMessage('requestNextRelatedIntent', {});
+        flash('Next related intent');
+    }
+
+    function showSelectedIntentTrafficAction() {
+        hoverMode = hoverModeNone;
+        sendMessage('requestSelectedIntentTraffic', {});
+        flash('Monitoring selected intent');
+    }
 
     function showDeviceLinkFlowsAction() {
         hoverMode = hoverModeFlows;
@@ -2010,13 +2022,17 @@
     }
 
     function nodeMouseOver(d) {
-        hovered = d;
-        requestTrafficForMode();
+        if (hovered != d) {
+            hovered = d;
+            requestTrafficForMode();
+        }
     }
 
     function nodeMouseOut(d) {
-        hovered = null;
-        requestTrafficForMode();
+        if (hovered != null) {
+            hovered = null;
+            requestTrafficForMode();
+        }
     }
 
     function addHostIcon(node, radius, iid) {
@@ -2498,7 +2514,7 @@
         wsTrace('rx', msg);
     }
     function wsTrace(rxtx, msg) {
-        console.log('[' + rxtx + '] ' + msg);
+        // console.log('[' + rxtx + '] ' + msg);
     }
 
     // NOTE: Temporary hardcoded example for showing detail pane
@@ -2620,7 +2636,6 @@
             emptySelect();
         } else if (nSel === 1) {
             singleSelect();
-            requestTrafficForMode();
         } else {
             multiSelect();
         }
@@ -2635,12 +2650,14 @@
     function singleSelect() {
         // NOTE: detail is shown from showDetails event callback
         requestDetails();
+        cancelTraffic();
         requestTrafficForMode();
     }
 
     function multiSelect() {
         haveDetails = true;
         populateMultiSelect();
+        cancelTraffic();
         requestTrafficForMode();
     }
 
@@ -2738,7 +2755,7 @@
     function addSingleSelectActions(data) {
         detailPane.append('hr');
         // always want to allow 'show traffic'
-        addAction(detailPane, 'Show Related Traffic', showTrafficAction);
+        addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
 
         if (data.type === 'switch') {
             addAction(detailPane, 'Show Device Flows', showDeviceLinkFlowsAction);
@@ -2748,7 +2765,7 @@
     function addMultiSelectActions() {
         detailPane.append('hr');
         // always want to allow 'show traffic'
-        addAction(detailPane, 'Show Related Traffic', showTrafficAction);
+        addAction(detailPane, 'Show Related Traffic', showRelatedIntentsAction);
         // if exactly two hosts are selected, also want 'add host intent'
         if (nSel() === 2 && allSelectionsClass('host')) {
             addAction(detailPane, 'Create Host-to-Host Flow', addHostIntentAction);