GUI -- Re-instated Packet / Optical Layer filters.
- Adding ONOS instance fly-in pane. Note: still WIP.
- refactored onos.ui.addFloatingPanel to allow TL vs. TR.
- added instance pane to topo view.
- implemented addInstance() event.
- refactored event tracing.
- added instances test scenario.

Change-Id: I58d9769afa8aee9079ec778496cbc47bef329608
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_10_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_10_onos.json
new file mode 100644
index 0000000..4a289d8
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_10_onos.json
@@ -0,0 +1,15 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000ffffffff0003/4-of:0000ffffffffff03/1",
+    "type": "pktopt",
+    "linkWidth": 2,
+    "src": "of:0000ffffffff0003",
+    "srcPort": "4",
+    "dst": "of:0000ffffffffff03",
+    "dstPort": "1",
+    "props" : {
+      "BW": "90 Gb"
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_11_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_11_onos.json
new file mode 100644
index 0000000..70dc216
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_11_onos.json
@@ -0,0 +1,15 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000ffffffff0003/9-of:0000ffffffff0007/2",
+    "type": "direct",
+    "linkWidth": 2,
+    "src": "of:0000ffffffff0003",
+    "srcPort": "9",
+    "dst": "of:0000ffffffff0007",
+    "dstPort": "2",
+    "props" : {
+      "BW": "120 Gb"
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_12_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_12_onos.json
new file mode 100644
index 0000000..78e6a39
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_12_onos.json
@@ -0,0 +1,15 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000ffffffff0008/2-of:0000ffffffff0003/1",
+    "type": "direct",
+    "linkWidth": 2,
+    "src": "of:0000ffffffff0008",
+    "srcPort": "2",
+    "dst": "of:0000ffffffff0003",
+    "dstPort": "1",
+    "props" : {
+      "BW": "70 Gb"
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_13_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_13_onos.json
new file mode 100644
index 0000000..d9d27e7
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_13_onos.json
@@ -0,0 +1,15 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000ffffffff0008/4-of:0000ffffffff0007/1",
+    "type": "direct",
+    "linkWidth": 2,
+    "src": "of:0000ffffffff0008",
+    "srcPort": "4",
+    "dst": "of:0000ffffffff0007",
+    "dstPort": "1",
+    "props" : {
+      "BW": "90 Gb"
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_14_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_14_onos.json
new file mode 100644
index 0000000..89435bc
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_14_onos.json
@@ -0,0 +1,15 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000ffffffffff08/4-of:0000ffffffffff03/1",
+    "type": "optical",
+    "linkWidth": 2,
+    "src": "of:0000ffffffffff08",
+    "srcPort": "4",
+    "dst": "of:0000ffffffffff03",
+    "dstPort": "1",
+    "props" : {
+      "BW": "90 Gb"
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_15_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_15_onos.json
new file mode 100644
index 0000000..af031a6
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_15_onos.json
@@ -0,0 +1,17 @@
+{
+  "event": "addHost",
+  "payload": {
+    "id": "0E:2A:69:30:13:aa/-1",
+    "ingress": "0E:2A:69:30:13:aa/-1/0-of:0000ffffffff0008/101",
+    "egress": "of:0000ffffffff0008/101-0E:2A:69:30:13:aa/-1/0",
+    "cp": {
+      "device": "of:0000ffffffff0008",
+      "port": 101
+    },
+    "labels": [
+      "12.13.14.15",
+      "0E:2A:69:30:13:aa"
+    ],
+    "props": {}
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_16_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_16_onos.json
new file mode 100644
index 0000000..6efcda9
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_16_onos.json
@@ -0,0 +1,17 @@
+{
+  "event": "addHost",
+  "payload": {
+    "id": "0E:2A:69:30:13:88/-1",
+    "ingress": "0E:2A:69:30:13:88/-1/0-of:0000ffffffff0007/101",
+    "egress": "of:0000ffffffff0007/101-0E:2A:69:30:13:86/-1/0",
+    "cp": {
+      "device": "of:0000ffffffff0007",
+      "port": 101
+    },
+    "labels": [
+      "4.5.7.6",
+      "0E:2A:69:30:13:88"
+    ],
+    "props": {}
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_17_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_17_onos.json
new file mode 100644
index 0000000..a5ee5c9
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_17_onos.json
@@ -0,0 +1,17 @@
+{
+  "event": "addHost",
+  "payload": {
+    "id": "0E:2A:69:30:13:86/-1",
+    "ingress": "0E:2A:69:30:13:86/-1/0-of:0000ffffffff0003/101",
+    "egress": "of:0000ffffffff0003/101-0E:2A:69:30:13:86/-1/0",
+    "cp": {
+      "device": "of:0000ffffffff0003",
+      "port": 101
+    },
+    "labels": [
+      "1.2.3.4",
+      "0E:2A:69:30:13:86"
+    ],
+    "props": {}
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_1_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_1_onos.json
new file mode 100644
index 0000000..d7f69d3
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_1_onos.json
@@ -0,0 +1,11 @@
+{
+  "event": "addInstance",
+  "payload": {
+    "id": "local",
+    "online": true,
+    "labels": [
+      "local",
+      "127.0.0.1"
+    ]
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_2_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_2_onos.json
new file mode 100644
index 0000000..be2aefb
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_2_onos.json
@@ -0,0 +1,11 @@
+{
+  "event": "addInstance",
+  "payload": {
+    "id": "onos-2",
+    "online": true,
+    "labels": [
+      "onos-2",
+      "192.168.2.2"
+    ]
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_3_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_3_onos.json
new file mode 100644
index 0000000..bfa1785
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_3_onos.json
@@ -0,0 +1,11 @@
+{
+  "event": "addInstance",
+  "payload": {
+    "id": "onos-3",
+    "online": false,
+    "labels": [
+      "onos-3",
+      "192.168.3.3"
+    ]
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_4_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_4_onos.json
new file mode 100644
index 0000000..f1abeeb
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_4_onos.json
@@ -0,0 +1,18 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000ffffffff0008",
+    "type": "switch",
+    "online": true,
+    "master": "onos-3",
+    "labels": [
+      "0000ffffffff0008",
+      "FF:FF:FF:FF:00:08",
+      "sw-8"
+    ],
+    "metaUi": {
+      "x": 734,
+      "y": 477
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_5_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_5_onos.json
new file mode 100644
index 0000000..352a835
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_5_onos.json
@@ -0,0 +1,18 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000ffffffff0003",
+    "type": "switch",
+    "online": true,
+    "master": "local",
+    "labels": [
+      "0000ffffffff0003",
+      "FF:FF:FF:FF:00:03",
+      "sw-3"
+    ],
+    "metaUi": {
+      "x": 282,
+      "y": 503
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_6_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_6_onos.json
new file mode 100644
index 0000000..2f58f61
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_6_onos.json
@@ -0,0 +1,18 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000ffffffff0007",
+    "type": "switch",
+    "online": true,
+    "master": "onos-2",
+    "labels": [
+      "0000ffffffff0007",
+      "FF:FF:FF:FF:00:07",
+      "sw-7"
+    ],
+    "metaUi": {
+      "x": 530,
+      "y": 330
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_7_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_7_onos.json
new file mode 100644
index 0000000..33f2967
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_7_onos.json
@@ -0,0 +1,18 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000ffffffffff08",
+    "type": "roadm",
+    "online": true,
+    "master": "onos-3",
+    "labels": [
+      "0000ffffffffff08",
+      "FF:FF:FF:FF:FF:08",
+      "opt-8"
+    ],
+    "metaUi": {
+      "x": 734,
+      "y": 577
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_8_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_8_onos.json
new file mode 100644
index 0000000..dfff514
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_8_onos.json
@@ -0,0 +1,18 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000ffffffffff03",
+    "type": "roadm",
+    "online": true,
+    "master": "local",
+    "labels": [
+      "0000ffffffffff03",
+      "FF:FF:FF:FF:FF:03",
+      "opt-3"
+    ],
+    "metaUi": {
+      "x": 282,
+      "y": 603
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/ev_9_onos.json b/web/gui/src/main/webapp/json/ev/instances/ev_9_onos.json
new file mode 100644
index 0000000..115858a
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/ev_9_onos.json
@@ -0,0 +1,15 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000ffffffff0008/4-of:0000ffffffffff08/1",
+    "type": "pktopt",
+    "linkWidth": 2,
+    "src": "of:0000ffffffff0008",
+    "srcPort": "4",
+    "dst": "of:0000ffffffffff08",
+    "dstPort": "1",
+    "props" : {
+      "BW": "90 Gb"
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/instances/scenario.json b/web/gui/src/main/webapp/json/ev/instances/scenario.json
new file mode 100644
index 0000000..807a51b2
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/instances/scenario.json
@@ -0,0 +1,16 @@
+{
+  "comments": [
+    "Developing ONOS instance visualization"
+  ],
+  "title": "ONOS Instance Scenario",
+  "params": {
+    "lastAuto": 17
+  },
+  "description": [
+    "Visualizing ONOS instances.",
+    "",
+    "Press 'S' to load initial events.",
+    "",
+    "Press spacebar to complete the scenario..."
+  ]
+}
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 7632148..87aa63d 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -699,6 +699,16 @@
         // ..........................................................
         // UI API
 
+        var fpConfig = {
+            TR: {
+                side: 'right'
+
+            },
+            TL: {
+                side: 'left'
+            }
+        };
+
         uiApi = {
             addLib: function (libName, api) {
                 // TODO: validation of args
@@ -713,6 +723,7 @@
              */
             addFloatingPanel: function (id, position) {
                 var pos = position || 'TR',
+                    cfg = fpConfig[pos],
                     el,
                     fp;
 
@@ -723,22 +734,37 @@
 
                 el = $floatPanels.append('div')
                     .attr('id', id)
-                    .attr('class', 'fpanel');
+                    .attr('class', 'fpanel')
+                    .style('opacity', 0);
+
+                // has to be called after el is set.
+                el.style(cfg.side, pxHide());
+
+                function pxShow() {
+                    return '20px';
+                }
+                function pxHide() {
+                    return (-20 - widthVal()) + 'px';
+                }
+                function widthVal() {
+                    return el.style('width').replace(/px$/, '');
+                }
 
                 fp = {
                     id: id,
                     el: el,
                     pos: pos,
+
                     show: function () {
                         console.log('show pane: ' + id);
                         el.transition().duration(750)
-                            .style('right', '20px')
+                            .style(cfg.side, pxShow())
                             .style('opacity', 1);
                     },
                     hide: function () {
                         console.log('hide pane: ' + id);
                         el.transition().duration(750)
-                            .style('right', '-320px')
+                            .style(cfg.side, pxHide())
                             .style('opacity', 0);
                     },
                     empty: function () {
@@ -746,6 +772,12 @@
                     },
                     append: function (what) {
                         return el.append(what);
+                    },
+                    width: function (w) {
+                        if (w === undefined) {
+                            return widthVal();
+                        }
+                        el.style('width', w);
                     }
                 };
                 fpanels[id] = fp;
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 0dcb470..f485e1e 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -197,6 +197,39 @@
     border: 0;
 }
 
+/* ONOS instance stuff */
+
+#topo-oibox {
+    width: 80px;
+}
+
+#topo-oibox .onosInst {
+    margin: 6px 0;
+    padding: 3px;
+    width: 80%;
+    left: 10%;
+    height: 40px;
+    cursor: pointer;
+
+    /* theme-related */
+    color: #444;
+    background-color: #ccc;
+    border: 2px dashed #aaa;
+}
+
+
+#topo-oibox .onosInst.online {
+    /* theme-related */
+    color: #113;
+    background-color: #bbf;
+    border: 2px solid #555;
+}
+
+#topo svg .suppressed,
+#topo-oibox .suppressed {
+    opacity: 0.2;
+}
+
 /* Web Socket Closed Mask (starts hidden) */
 
 #topo-mask {
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index ea3c6e6..e14f020 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -155,11 +155,14 @@
         sid = 0,
         deviceLabelIndex = 0,
         hostLabelIndex = 0,
-        detailPane,
-        selectOrder = [],
         selections = {},
+        selectOrder = [],
         hovered = null,
+        detailPane,
         antTimer = null,
+        onosInstances = {},
+        onosOrder = [],
+        oiBox,
 
         viewMode = 'showAll',
         portLabelsOn = false;
@@ -198,11 +201,19 @@
         }
     }
 
+    function evTrace(data) {
+        fnTrace(data.event, data.payload.id);
+    }
+
     // ==============================
     // Key Callbacks
 
     function testMe(view) {
-        view.alert('test');
+        //view.alert('test');
+        detailPane.show();
+        setTimeout(detailPane.hide, 2000);
+        oiBox.show();
+        setTimeout(oiBox.hide, 2000);
     }
 
     function abortIfLive() {
@@ -303,25 +314,61 @@
     // ==============================
     // Radio Button Callbacks
 
+    var layerLookup = {
+        host: {
+            endstation: 'pkt', // default, if host event does not define type
+            bgpSpeaker: 'pkt'
+        },
+        device: {
+            switch: 'pkt',
+            roadm: 'opt'
+        },
+        link: {
+            hostLink: 'pkt',
+            direct: 'pkt',
+            optical: 'opt'
+        }
+    };
+
+    function inLayer(d, layer) {
+        var look = layerLookup[d.class],
+            lyr = look && look[d.type];
+        return lyr === layer;
+    }
+
+    function unsuppressLayer(which) {
+        node.each(function (d) {
+            var node = d.el;
+            if (inLayer(d, which)) {
+                node.classed('suppressed', false);
+            }
+        });
+
+        link.each(function (d) {
+            var link = d.el;
+            if (inLayer(d, which)) {
+                link.classed('suppressed', false);
+            }
+        });
+    }
+
     function showAllLayers() {
-//        network.node.classed('inactive', false);
-//        network.link.classed('inactive', false);
+        node.classed('suppressed', false);
+        link.classed('suppressed', false);
 //        d3.selectAll('svg .port').classed('inactive', false);
 //        d3.selectAll('svg .portText').classed('inactive', false);
-        // TODO ...
-        network.view.alert('showAllLayers() callback');
     }
 
     function showPacketLayer() {
-        showAllLayers();
-        // TODO ...
-        network.view.alert('showPacketLayer() callback');
+        node.classed('suppressed', true);
+        link.classed('suppressed', true);
+        unsuppressLayer('pkt');
     }
 
     function showOpticalLayer() {
-        showAllLayers();
-        // TODO ...
-        network.view.alert('showOpticalLayer() callback');
+        node.classed('suppressed', true);
+        link.classed('suppressed', true);
+        unsuppressLayer('opt');
     }
 
     // ==============================
@@ -351,7 +398,7 @@
     }
 
     var eventDispatch = {
-        addInstance: stillToImplement,
+        addInstance: addInstance,
         addDevice: addDevice,
         addLink: addLink,
         addHost: addHost,
@@ -371,8 +418,21 @@
         showTraffic: showTraffic
     };
 
+    function addInstance(data) {
+        evTrace(data);
+        var inst = data.payload,
+            id = inst.id;
+        if (onosInstances[id]) {
+            logicError('ONOS instance already added: ' + id);
+            return;
+        }
+        onosInstances[id] = inst;
+        onosOrder.push(inst);
+        updateInstances();
+    }
+
     function addDevice(data) {
-        fnTrace('addDevice', data.payload.id);
+        evTrace(data);
         var device = data.payload,
             nodeData = createDeviceNode(device);
         network.nodes.push(nodeData);
@@ -382,7 +442,7 @@
     }
 
     function addLink(data) {
-        fnTrace('addLink', data.payload.id);
+        evTrace(data);
         var link = data.payload,
             lnk = createLink(link);
         if (lnk) {
@@ -394,7 +454,7 @@
     }
 
     function addHost(data) {
-        fnTrace('addHost', data.payload.id);
+        evTrace(data);
         var host = data.payload,
             node = createHostNode(host),
             lnk;
@@ -415,7 +475,7 @@
 
     // TODO: fold updateX(...) methods into one base method; remove duplication
     function updateDevice(data) {
-        fnTrace('updateDevice', data.payload.id);
+        evTrace(data);
         var device = data.payload,
             id = device.id,
             nodeData = network.lookup[id];
@@ -428,7 +488,7 @@
     }
 
     function updateLink(data) {
-        fnTrace('updateLink', data.payload.id);
+        evTrace(data);
         var link = data.payload,
             id = link.id,
             linkData = network.lookup[id];
@@ -441,7 +501,7 @@
     }
 
     function updateHost(data) {
-        fnTrace('updateHost', data.payload.id);
+        evTrace(data);
         var host = data.payload,
             id = host.id,
             hostData = network.lookup[id];
@@ -455,7 +515,7 @@
 
     // TODO: fold removeX(...) methods into base method - remove dup code
     function removeLink(data) {
-        fnTrace('removeLink', data.payload.id);
+        evTrace(data);
         var link = data.payload,
             id = link.id,
             linkData = network.lookup[id];
@@ -467,7 +527,7 @@
     }
 
     function removeHost(data) {
-        fnTrace('removeHost', data.payload.id);
+        evTrace(data);
         var host = data.payload,
             id = host.id,
             hostData = network.lookup[id];
@@ -479,14 +539,14 @@
     }
 
     function showDetails(data) {
-        fnTrace('showDetails', data.payload.id);
+        evTrace(data);
         populateDetails(data.payload);
         detailPane.show();
     }
 
     function showPath(data) {
         // TODO: review - making sure we are handling the payload correctly.
-        fnTrace('showPath', data.payload.id);
+        evTrace(data);
         var links = data.payload.links,
             s = [ data.event + "\n" + links.length ];
         links.forEach(function (d, i) {
@@ -503,7 +563,7 @@
     }
 
     function showTraffic(data) {
-        fnTrace('showTraffic', data.payload.id);
+        evTrace(data);
         var paths = data.payload.paths;
 
         // Revert any links hilighted previously.
@@ -602,6 +662,30 @@
 
 
     // ==============================
+    // onos instance panel functions
+
+    function updateInstances() {
+        var onoses = oiBox.el.selectAll('.onosInst')
+            .data(onosOrder, function (d) { return d.id; });
+
+        // operate on existing onoses if necessary
+
+        var entering = onoses.enter()
+            .append('div')
+            .attr('class', 'onosInst')
+            .classed('online', function (d) { return d.online; })
+            .text(function (d) { return d.id; });
+
+        // operate on existing + new onoses here
+
+        // the departed...
+        var exiting = onoses.exit()
+            .transition()
+            .style('opacity', 0)
+            .remove();
+    }
+
+    // ==============================
     // force layout modification functions
 
     function translate(x, y) {
@@ -760,11 +844,9 @@
         // Augment as needed...
         node.class = 'host';
         if (!node.type) {
-            // TODO: perhaps type would be: {phone, tablet, laptop, endstation} ?
             node.type = 'endstation';
         }
         node.svgClass = 'node host';
-        // TODO: consider placing near its switch, if [x,y] not defined
         positionNode(node);
 
         // cache label array length
@@ -1352,21 +1434,21 @@
     function addSingleSelectActions() {
         detailPane.append('hr');
         // always want to allow 'show traffic'
-        addAction('Show Traffic', showTrafficAction);
+        addAction(detailPane, 'Show Traffic', showTrafficAction);
     }
 
     function addMultiSelectActions() {
         detailPane.append('hr');
         // always want to allow 'show traffic'
-        addAction('Show Traffic', showTrafficAction);
+        addAction(detailPane, 'Show Traffic', showTrafficAction);
         // if exactly two hosts are selected, also want 'add host intent'
         if (nSel() === 2 && allSelectionsClass('host')) {
-            addAction('Add Host Intent', addIntentAction);
+            addAction(detailPane, 'Add Host Intent', addIntentAction);
         }
     }
 
-    function addAction(text, cb) {
-        detailPane.append('div')
+    function addAction(panel, text, cb) {
+        panel.append('div')
             .classed('actionBtn', true)
             .text(text)
             .on('click', cb);
@@ -1441,39 +1523,52 @@
     // TODO: toggle button (and other widgets in the masthead) should be provided
     //  by the framework; not generated by the view.
 
-    var showTrafficOnHover,
-        doPanZoom;
+    var showInstances,
+        doPanZoom,
+        showTrafficOnHover;
 
     function addButtonBar(view) {
         var bb = d3.select('#mast')
             .append('span').classed('right', true).attr('id', 'bb');
 
-        doPanZoom = bb.append('span')
-            .classed('btn', true)
-            .text('Pan/Zoom')
-            .on('click', togglePanZoom);
+        function mkTogBtn(text, cb) {
+            return bb.append('span')
+                .classed('btn', true)
+                .text(text)
+                .on('click', cb);
+        }
 
-        showTrafficOnHover = bb.append('span')
-            .classed('btn', true)
-            .text('Show traffic on hover')
-            .on('click', toggleTrafficHover);
+        showInstances = mkTogBtn('Show Instances', toggleInst);
+        doPanZoom = mkTogBtn('Pan/Zoom', togglePanZoom);
+        showTrafficOnHover = mkTogBtn('Show traffic on hover', toggleTrafficHover);
     }
 
-    function toggleTrafficHover() {
-        showTrafficOnHover.classed('active', !trafficHover());
+    function instShown() {
+        return showInstances.classed('active');
     }
-
-    function trafficHover() {
-        return showTrafficOnHover.classed('active');
-    }
-
-    function togglePanZoom() {
-        doPanZoom.classed('active', !panZoom());
+    function toggleInst() {
+        showInstances.classed('active', !instShown());
+        if (instShown()) {
+            oiBox.show();
+        } else {
+            oiBox.hide();
+        }
     }
 
     function panZoom() {
         return doPanZoom.classed('active');
     }
+    function togglePanZoom() {
+        doPanZoom.classed('active', !panZoom());
+    }
+
+    function trafficHover() {
+        return showTrafficOnHover.classed('active');
+    }
+    function toggleTrafficHover() {
+        showTrafficOnHover.classed('active', !trafficHover());
+    }
+
 
     // ==============================
     // View life-cycle callbacks
@@ -1485,16 +1580,12 @@
             fpad = fcfg.pad,
             forceDim = [w - 2*fpad, h - 2*fpad];
 
-        // TODO: set trace api
-        //trace = onos.exported.webSockTrace;
-
         // NOTE: view.$div is a D3 selection of the view's div
         var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
         svg = view.$div.append('svg').attr('viewBox', viewBox);
         setSize(svg, view);
 
         zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
-
         setupZoomPan();
 
         // add blue glow filter to svg layer
@@ -1566,6 +1657,7 @@
             selectCb, atDragEnd, panZoom);
 
         // create mask layer for when we lose connection to server.
+        // TODO: this should be part of the framework
         mask = view.$div.append('div').attr('id','topo-mask');
         para(mask, 'Oops!');
         para(mask, 'Web-socket connection to server closed...');
@@ -1707,5 +1799,6 @@
     });
 
     detailPane = onos.ui.addFloatingPanel('topo-detail');
+    oiBox = onos.ui.addFloatingPanel('topo-oibox', 'TL');
 
 }(ONOS));