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/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java b/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
index 9703970..ad6d142 100644
--- a/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
@@ -43,10 +43,10 @@
                     .put("node", get(ClusterService.class).getLocalNode().ip().toString())
                     .put("version", get(CoreService.class).version().toString())
                     .put("nodes", get(ClusterService.class).getNodes().size())
-                    .put("devices", get(DeviceService.class).getDeviceCount())
-                    .put("links", get(LinkService.class).getLinkCount())
+                    .put("devices", topology.deviceCount())
+                    .put("links", topology.linkCount())
                     .put("hosts", get(HostService.class).getHostCount())
-                    .put("clusters", topologyService.getClusters(topology).size())
+                    .put("clusters", topology.clusterCount())
                     .put("paths", topology.pathCount())
                     .put("flows", get(FlowRuleService.class).getFlowRuleCount())
                     .put("intents", get(IntentService.class).getIntentCount()));
diff --git a/tools/package/bin/onos-service b/tools/package/bin/onos-service
index ae6d970..7ce1b15 100755
--- a/tools/package/bin/onos-service
+++ b/tools/package/bin/onos-service
@@ -4,7 +4,7 @@
 # -----------------------------------------------------------------------------
 
 #export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-7-openjdk-amd64/}
-export JAVA_OPTS="${JAVA_OPTS:--Xms256M -Xmx2048M}"
+export JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx2048m}"
 
 cd /opt/onos
 /opt/onos/apache-karaf-$KARAF_VERSION/bin/karaf "$@"
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 6c12400..91a820c 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
@@ -23,6 +23,7 @@
 import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.core.CoreService;
 import org.onlab.onos.mastership.MastershipService;
 import org.onlab.onos.net.Annotated;
 import org.onlab.onos.net.Annotations;
@@ -56,6 +57,8 @@
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.net.statistic.Load;
 import org.onlab.onos.net.statistic.StatisticService;
+import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyService;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.IpAddress;
 import org.slf4j.Logger;
@@ -117,8 +120,10 @@
     protected final IntentService intentService;
     protected final FlowRuleService flowService;
     protected final StatisticService statService;
+    protected final TopologyService topologyService;
 
     protected final ObjectMapper mapper = new ObjectMapper();
+    private final String version;
 
     // TODO: extract into an external & durable state; good enough for now and demo
     private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
@@ -138,6 +143,9 @@
         intentService = directory.get(IntentService.class);
         flowService = directory.get(FlowRuleService.class);
         statService = directory.get(StatisticService.class);
+        topologyService = directory.get(TopologyService.class);
+
+        version = directory.get(CoreService.class).version().toString();
     }
 
     // Retrieves the payload from the specified event.
@@ -419,6 +427,22 @@
         metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
     }
 
+    // Returns summary response.
+    protected ObjectNode summmaryMessage(long sid) {
+        Topology topology = topologyService.currentTopology();
+        return envelope("showSummary", sid,
+                        json("ONOS Summary", "node",
+                             new Prop("Devices", format(topology.deviceCount())),
+                             new Prop("Links", format(topology.linkCount())),
+                             new Prop("Hosts", format(hostService.getHostCount())),
+                             new Prop("Topology SCCs", format(topology.clusterCount())),
+                             new Prop("Paths", format(topology.pathCount())),
+                             new Separator(),
+                             new Prop("Intents", format(intentService.getIntentCount())),
+                             new Prop("Flows", format(flowService.getFlowRuleCount())),
+                             new Prop("Version", version.replace(".SNAPSHOT", "*"))));
+    }
+
     // Returns device details response.
     protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
         Device device = deviceService.getDevice(deviceId);
@@ -435,12 +459,12 @@
                              new Prop("S/W Version", device.swVersion()),
                              new Prop("Serial Number", device.serialNumber()),
                              new Separator(),
+                             new Prop("Master", master(deviceId)),
                              new Prop("Latitude", annot.value("latitude")),
                              new Prop("Longitude", annot.value("longitude")),
-                             new Prop("Ports", Integer.toString(portCount)),
-                             new Prop("Flows", Integer.toString(flowCount)),
                              new Separator(),
-                             new Prop("Master", master(deviceId))));
+                             new Prop("Ports", Integer.toString(portCount)),
+                             new Prop("Flows", Integer.toString(flowCount))));
     }
 
     protected int getFlowCount(DeviceId deviceId) {
@@ -641,6 +665,12 @@
         return format.format(value) + " " + unit;
     }
 
+    // Formats the given number into a string.
+    private String format(Number number) {
+        DecimalFormat format = new DecimalFormat("#,###");
+        return format.format(number);
+    }
+
     private boolean isInfrastructureEgress(Link link) {
         return link.src().elementId() instanceof DeviceId;
     }
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);
+            }
+        }
+    }
+
 }
 
diff --git a/web/gui/src/main/webapp/feedback.css b/web/gui/src/main/webapp/feedback.css
index 952688d..06716bb 100644
--- a/web/gui/src/main/webapp/feedback.css
+++ b/web/gui/src/main/webapp/feedback.css
@@ -23,7 +23,7 @@
 #feedback svg {
     position: absolute;
     bottom: 0;
-    opacity: 0.5;
+    opacity: 0.8;
 }
 
 #feedback svg g.feedbackItem {
@@ -31,14 +31,11 @@
 }
 
 #feedback svg g.feedbackItem rect {
-    fill: #888;
-    stroke: #666;
-    stroke-width: 3;
-    opacity: 0.5
+    fill: #ccc;
 }
 
 #feedback svg g.feedbackItem text {
-    fill: #000;
+    fill: #333;
     stroke: none;
     text-anchor: middle;
     alignment-baseline: middle;
diff --git a/web/gui/src/main/webapp/feedback.js b/web/gui/src/main/webapp/feedback.js
index 10f87c9..644202e 100644
--- a/web/gui/src/main/webapp/feedback.js
+++ b/web/gui/src/main/webapp/feedback.js
@@ -33,7 +33,7 @@
     var w = '100%',
         h = 200,
         fade = 200,
-        showFor = 500,
+        showFor = 1200,
         vb = '-200 -' + (h/2) + ' 400 ' + h,
         xpad = 20,
         ypad = 10;
diff --git a/web/gui/src/main/webapp/floatPanel.css b/web/gui/src/main/webapp/floatPanel.css
index bacf29e..2a9b8a2 100644
--- a/web/gui/src/main/webapp/floatPanel.css
+++ b/web/gui/src/main/webapp/floatPanel.css
@@ -24,8 +24,8 @@
     position: absolute;
     z-index: 100;
     display: block;
-    top: 10%;
-    width: 280px;
+    top: 64px;
+    width: 260px;
     right: -300px;
     opacity: 0;
     background-color: rgba(255,255,255,0.8);
diff --git a/web/gui/src/main/webapp/keymap.css b/web/gui/src/main/webapp/keymap.css
index 7e6ade0..e6b9715 100644
--- a/web/gui/src/main/webapp/keymap.css
+++ b/web/gui/src/main/webapp/keymap.css
@@ -31,10 +31,10 @@
 }
 
 #keymap svg text.title {
-    font-size: 12pt;
+    font-size: 10pt;
     font-style: italic;
     text-anchor: middle;
-    fill: #444;
+    fill: #999;
 }
 
 #keymap svg g.keyItem {
@@ -47,17 +47,17 @@
 }
 
 #keymap svg text {
-    font-size: 10pt;
+    font-size: 7pt;
     alignment-baseline: middle;
 }
 
 #keymap svg text.key {
-    font-size: 10pt;
-    fill: #8aa;
+    font-size: 7pt;
+    fill: #add;
 }
 
 #keymap svg text.desc {
-    font-size: 10pt;
-    fill: #888;
+    font-size: 7pt;
+    fill: #aaa;
 }
 
diff --git a/web/gui/src/main/webapp/keymap.js b/web/gui/src/main/webapp/keymap.js
index 85f2539..3d96a56 100644
--- a/web/gui/src/main/webapp/keymap.js
+++ b/web/gui/src/main/webapp/keymap.js
@@ -35,9 +35,9 @@
         fade = 500,
         vb = '-220 -220 440 440',
         paneW = 400,
-        paneH = 340,
+        paneH = 280,
         offy = 65,
-        dy = 20,
+        dy = 14,
         offKey = 40,
         offDesc = offKey + 50,
         lineW = paneW - (2*offKey);
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index d2a7baa..a9cb1b0 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -763,7 +763,8 @@
                 var pos = position || 'TR',
                     cfg = fpConfig[pos],
                     el,
-                    fp;
+                    fp,
+                    on = false;
 
                 if (fpanels[id]) {
                     buildError('Float panel with id "' + id + '" already exists.');
@@ -792,15 +793,20 @@
                     id: id,
                     el: el,
                     pos: pos,
+                    isVisible: function () {
+                        return on;
+                    },
 
                     show: function () {
                         console.log('show pane: ' + id);
+                        on = true;
                         el.transition().duration(750)
                             .style(cfg.side, pxShow())
                             .style('opacity', 1);
                     },
                     hide: function () {
                         console.log('hide pane: ' + id);
+                        on = false;
                         el.transition().duration(750)
                             .style(cfg.side, pxHide())
                             .style('opacity', 0);
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 809458d..d982ee2 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -177,10 +177,66 @@
     font-size: 9pt;
 }
 
+/* Fly-in summary pane */
+
+#topo-summary {
+    /* gets base CSS from .fpanel in floatPanel.css */
+    top: 64px;
+}
+
+#topo-summary svg {
+    display: inline-block;
+    width: 42px;
+    height: 42px;
+}
+
+#topo-summary svg .glyphIcon {
+    fill: black;
+    stroke: none;
+    fill-rule: evenodd;
+}
+
+#topo-summary h2 {
+    position: absolute;
+    margin: 0px 4px;
+    top: 20px;
+    left: 50px;
+    color: black;
+}
+
+#topo-summary h3 {
+    margin: 0px 4px;
+    top: 20px;
+    left: 50px;
+    color: black;
+}
+
+#topo-summary p, table {
+    margin: 4px 4px;
+}
+
+#topo-summary td.label {
+    font-style: italic;
+    color: #777;
+    padding-right: 12px;
+}
+
+#topo-summary td.value {
+}
+
+#topo-summary hr {
+    height: 1px;
+    color: #ccc;
+    background-color: #ccc;
+    border: 0;
+}
+
 /* Fly-in details pane */
 
 #topo-detail {
-/* gets base CSS from .fpanel in floatPanel.css */
+    /* gets base CSS from .fpanel in floatPanel.css */
+    top: 320px;
+
 }
 
 #topo-detail svg {
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 9db6461..44b90f0 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -141,6 +141,8 @@
         S: injectStartupEvents,
         space: injectTestEvent,
 
+        O: [toggleSummary, 'Toggle ONOS summary pane'],
+        I: [toggleInstances, 'Toggle ONOS instances pane'],
         B: [toggleBg, 'Toggle background image'],
         L: [cycleLabels, 'Cycle Device labels'],
         P: togglePorts,
@@ -182,6 +184,7 @@
         selections = {},
         selectOrder = [],
         hovered = null,
+        summaryPane,
         detailPane,
         antTimer = null,
         onosInstances = {},
@@ -329,7 +332,7 @@
         if (hoverMode === hoverModes.length) {
             hoverMode = 0;
         }
-        view.flash('Hover Mode: ' + hoverModes[hoverMode]);
+        view.flash('Mode: ' + hoverModes[hoverMode]);
     }
 
     function togglePorts(view) {
@@ -347,8 +350,12 @@
     function handleEscape(view) {
         if (oiShowMaster) {
             cancelAffinity();
-        } else {
+        } else if (detailPane.isVisible()) {
             deselectAll();
+        } else if (oiBox.isVisible()) {
+            oiBox.hide();
+        } else if (summaryPane.isVisible()) {
+            cancelSummary();
         }
     }
 
@@ -585,6 +592,7 @@
         removeHost: removeHost,
 
         showDetails: showDetails,
+        showSummary: showSummary,
         showTraffic: showTraffic
     };
 
@@ -737,6 +745,12 @@
         }
     }
 
+    function showSummary(data) {
+        evTrace(data);
+        populateSummary(data.payload);
+        summaryPane.show();
+    }
+
     function showDetails(data) {
         evTrace(data);
         populateDetails(data.payload);
@@ -824,6 +838,33 @@
         return true;
     }
 
+
+    function toggleInstances() {
+        if (!oiBox.isVisible()) {
+            oiBox.show();
+        } else {
+            oiBox.hide();
+        }
+    }
+
+    function toggleSummary() {
+        if (!summaryPane.isVisible()) {
+            requestSummary();
+        } else {
+            cancelSummary();
+        }
+    }
+
+    // request overall summary data
+    function requestSummary() {
+        sendMessage('requestSummary', {});
+    }
+
+    function cancelSummary() {
+        sendMessage('cancelSummary', {});
+        summaryPane.hide();
+    }
+
     // request details for the selected element
     // invoked from selection of a single node.
     function requestDetails() {
@@ -845,16 +886,20 @@
     }
 
     function showTrafficAction() {
-        // force intents hover mode
+        cancelTraffic();
         hoverMode = 1;
         showSelectTraffic();
         network.view.flash('Related Traffic');
     }
 
+    function cancelTraffic() {
+        sendMessage('cancelTraffic', {});
+    }
+
     function showSelectTraffic() {
         // if nothing is hovered over, and nothing selected, send cancel request
         if (!hovered && nSel() === 0) {
-            sendMessage('cancelTraffic', {});
+            cancelTraffic();
             return;
         }
 
@@ -870,12 +915,13 @@
     }
 
     function showAllTrafficAction() {
+        cancelTraffic();
         sendMessage('requestAllTraffic', {});
         network.view.flash('All Traffic');
     }
 
     function showDeviceLinkFlowsAction() {
-        // force intents hover mode
+        cancelTraffic();
         hoverMode = 2;
         showDeviceLinkFlows();
         network.view.flash('Device Flows');
@@ -884,7 +930,7 @@
     function showDeviceLinkFlows() {
         // if nothing is hovered over, and nothing selected, send cancel request
         if (!hovered && nSel() === 0) {
-            sendMessage('cancelTraffic', {});
+            cancelTraffic();
             return;
         }
         var hoverId = (flowsHover() && hovered && hovered.class === 'device') ?
@@ -907,7 +953,6 @@
             'xlink:href': iid,
             width: dim,
             height: dim
-
         });
     }
 
@@ -940,6 +985,15 @@
             });
             var dim = 30;
             appendGlyph(svg, 2, 2, 30, '#node');
+            svg.append('use')
+                .attr({
+                    class: 'birdBadge',
+                    transform: translate(8,10),
+                    'xlink:href': '#bird',
+                    width: 18,
+                    height: 18,
+                    fill: '#fff'
+                });
 
             $('<div>').attr('class', 'onosTitle').text(d.id).appendTo(el);
 
@@ -1720,6 +1774,8 @@
 
             webSock.ws.onopen = function() {
                 noWebSock(false);
+                requestSummary();
+                oiBox.show();
             };
 
             webSock.ws.onmessage = function(m) {
@@ -1881,12 +1937,17 @@
         updateDetailPane();
     }
 
+    // update the state of the sumary pane
+    function updateSummaryPane() {
+
+    }
+
     // update the state of the detail pane, based on current selections
     function updateDetailPane() {
         var nSel = selectOrder.length;
         if (!nSel) {
             detailPane.hide();
-            showTrafficAction();        // sends cancelTraffic event
+            cancelTraffic();
         } else if (nSel === 1) {
             singleSelect();
         } else {
@@ -1936,6 +1997,40 @@
         addMultiSelectActions();
     }
 
+    // TODO: refactor to consolidate with populateDetails
+    function populateSummary(data) {
+        summaryPane.empty();
+
+        var svg = summaryPane.append('svg'),
+            iid = iconGlyphUrl(data);
+
+        var title = summaryPane.append('h2'),
+            table = summaryPane.append('table'),
+            tbody = table.append('tbody');
+
+        appendGlyph(svg, 0, 0, 40, iid);
+
+        svg.append('use')
+            .attr({
+                class: 'birdBadge',
+                transform: translate(8,12),
+                'xlink:href': '#bird',
+                width: 24,
+                height: 24,
+                fill: '#fff'
+            });
+
+        title.text('ONOS Summary');
+
+        data.propOrder.forEach(function(p) {
+            if (p === '-') {
+                addSep(tbody);
+            } else {
+                addProp(tbody, p, data.props[p]);
+            }
+        });
+    }
+
     function populateDetails(data) {
         detailPane.empty();
 
@@ -2056,7 +2151,7 @@
     // TODO: toggle button (and other widgets in the masthead) should be provided
     //  by the framework; not generated by the view.
 
-    var showInstances;
+    //var showInstances;
 
     function addButtonBar(view) {
         var bb = d3.select('#mast')
@@ -2069,20 +2164,20 @@
                 .on('click', cb);
         }
 
-        showInstances = mkTogBtn('Show Instances', toggleInst);
+        //showInstances = mkTogBtn('Show Instances', toggleInst);
     }
 
-    function instShown() {
-        return showInstances.classed('active');
-    }
-    function toggleInst() {
-        showInstances.classed('active', !instShown());
-        if (instShown()) {
-            oiBox.show();
-        } else {
-            oiBox.hide();
-        }
-    }
+    //function instShown() {
+    //    return showInstances.classed('active');
+    //}
+    //function toggleInst() {
+    //    showInstances.classed('active', !instShown());
+    //    if (instShown()) {
+    //        oiBox.show();
+    //    } else {
+    //        oiBox.hide();
+    //    }
+    //}
 
     function panZoom() {
         return false;
@@ -2370,6 +2465,7 @@
         resize: resize
     });
 
+    summaryPane = onos.ui.addFloatingPanel('topo-summary');
     detailPane = onos.ui.addFloatingPanel('topo-detail');
     oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');