GUI -- Completed ONOS Instance panel, and handling of updateInstance and removeInstance.
- minor refactorings and other cleanup.

Change-Id: I4d0e467f71269f7fb91175e78d2c6af750bf9aad
diff --git a/web/gui/src/main/webapp/feedback.css b/web/gui/src/main/webapp/feedback.css
index 06716bb..a912b41 100644
--- a/web/gui/src/main/webapp/feedback.css
+++ b/web/gui/src/main/webapp/feedback.css
@@ -20,6 +20,10 @@
  @author Simon Hunt
  */
 
+#feedback {
+    z-index: 1400;
+}
+
 #feedback svg {
     position: absolute;
     bottom: 0;
diff --git a/web/gui/src/main/webapp/json/ev/colors/ev_13_onos.json b/web/gui/src/main/webapp/json/ev/colors/ev_13_onos.json
new file mode 100644
index 0000000..5633802
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/colors/ev_13_onos.json
@@ -0,0 +1,14 @@
+{
+  "event": "updateInstance",
+  "payload": {
+    "id": "onos-slave",
+    "ip": "192.168.24.11",
+    "online": true,
+    "uiAttached": false,
+    "switches": 103,
+    "labels": [
+      "onos-slave",
+      "192.168.24.11"
+    ]
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/colors/ev_14_onos.json b/web/gui/src/main/webapp/json/ev/colors/ev_14_onos.json
new file mode 100644
index 0000000..f8a9186
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/colors/ev_14_onos.json
@@ -0,0 +1,14 @@
+{
+  "event": "removeInstance",
+  "payload": {
+    "id": "onos-leader",
+    "ip": "192.168.0.5",
+    "online": false,
+    "uiAttached": false,
+    "switches": 0,
+    "labels": [
+      "onos-leader",
+      "192.168.0.5"
+    ]
+  }
+}
diff --git a/web/gui/src/main/webapp/keymap.css b/web/gui/src/main/webapp/keymap.css
index e6b9715..4f465e1 100644
--- a/web/gui/src/main/webapp/keymap.css
+++ b/web/gui/src/main/webapp/keymap.css
@@ -20,6 +20,10 @@
  @author Simon Hunt
  */
 
+#keymap {
+    z-index: 1300;
+}
+
 #keymap svg {
     position: absolute;
     bottom: 40px;
diff --git a/web/gui/src/main/webapp/onos2.css b/web/gui/src/main/webapp/onos2.css
index 0920bac..32cdbe8 100644
--- a/web/gui/src/main/webapp/onos2.css
+++ b/web/gui/src/main/webapp/onos2.css
@@ -49,7 +49,7 @@
 #alerts {
     display: none;
     position: absolute;
-    z-index: 2000;
+    z-index: 1200;
     opacity: 0.65;
     background-color: #006;
     color: white;
@@ -83,10 +83,12 @@
     color: #66d;
 }
 
+#floatPanels {
+    z-index: 1100;
+}
 
 #flyout {
     position: absolute;
-    z-index: 100;
     display: block;
     top: 10%;
     width: 280px;
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 2405988..6db968b 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -785,8 +785,14 @@
                 function pxHide() {
                     return (-20 - widthVal()) + 'px';
                 }
+                function noPx(what) {
+                    return el.style(what).replace(/px$/, '');
+                }
                 function widthVal() {
-                    return el.style('width').replace(/px$/, '');
+                    return noPx('width');
+                }
+                function heightVal() {
+                    return noPx('height');
                 }
 
                 fp = {
@@ -822,6 +828,12 @@
                             return widthVal();
                         }
                         el.style('width', w + 'px');
+                    },
+                    height: function (h) {
+                        if (h === undefined) {
+                            return heightVal();
+                        }
+                        el.style('height', h + 'px');
                     }
                 };
                 fpanels[id] = fp;
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index 46c0d02..ef0baf5 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -318,8 +318,9 @@
 
 #topo-oibox div.onosInst {
     display: inline-block;
-    width: 120px;
-    height: 100px;
+    width: 170px;
+    height: 85px;
+    cursor: pointer;
 }
 
 #topo-oibox svg rect {
@@ -350,6 +351,22 @@
     fill: #fff;
 }
 
+#topo-oibox svg text {
+    text-anchor: middle;
+    fill: #888;
+}
+#topo-oibox .online svg text {
+    fill: #000;
+}
+
+#topo-oibox svg text.instTitle {
+    font-size: 11pt;
+    font-weight: bold;
+}
+#topo-oibox svg text.instLabel {
+    font-size: 9pt;
+    font-style: italic;
+}
 
 #topo-oibox .onosInst.mastership {
     opacity: 0.3;
@@ -361,66 +378,6 @@
     filter: url(#blue-glow);
 }
 
-/* ------------------------------------------------------ */
-/* ------------------------------------------------------ */
-/* ------------------------------------------------------ */
-
-#topo-oibox .onosInst_OLD {
-    position: relative;
-    width: 88%;
-    left: 4%;
-    height: 80px;
-    margin: 8px 0;
-    cursor: pointer;
-
-    -moz-border-radius: 12px;
-    border-radius: 12px;
-
-    /* theme-related */
-    color: #444;
-    background-color: #ccc;
-    border: 4px solid #aaa;
-}
-
-#topo-oibox .onosInst_OLD .onosTitle {
-    text-align: center;
-    font-size: 10pt;
-    margin-top: 6px;
-    color: #888;
-}
-
-#topo-oibox .onosInst_OLD.online .onosTitle {
-    color: black;
-}
-
-#topo-oibox .onosInst_OLD svg .glyphIcon {
-    opacity: 0.5;
-    fill: black;
-    stroke: none;
-    fill-rule: evenodd;
-}
-#topo-oibox .onosInst_OLD.online svg .glyphIcon {
-    opacity: 1;
-    fill: black;
-    stroke: none;
-    fill-rule: evenodd;
-}
-
-
-#topo-oibox .onosInst_OLD.online img {
-    opacity: 1.0;
-    padding: 3px;
-}
-
-#topo-oibox .onosInst_OLD img.ui {
-    opacity: 1;
-    position: absolute;
-    top: 3px;
-    right: 3px;
-    width: 20px;
-    height: 20px;
-}
-
 
 #topo svg .suppressed {
     opacity: 0.2;
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index cd30334..29a3773 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -580,7 +580,7 @@
         updateLink: updateLink,
         updateHost: updateHost,
 
-        removeInstance: stillToImplement,
+        removeInstance: removeInstance,
         removeDevice: stillToImplement,
         removeLink: removeLink,
         removeHost: removeHost,
@@ -715,6 +715,23 @@
     }
 
     // TODO: fold removeX(...) methods into base method - remove dup code
+    function removeInstance(data) {
+        evTrace(data);
+        var inst = data.payload,
+            id = inst.id,
+            instData = onosInstances[id];
+        if (instData) {
+            var idx = find(id, onosOrder, 'id');
+            if (idx >= 0) {
+                onosOrder.splice(idx, 1);
+            }
+            delete onosInstances[id];
+            updateInstances();
+        } else {
+            logicError('updateInstance lookup fail. ID = "' + id + '"');
+        }
+    }
+
     function removeLink(data) {
         evTrace(data);
         var link = data.payload,
@@ -793,7 +810,9 @@
     function stillToImplement(data) {
         var p = data.payload;
         note(data.event, p.id);
-        network.view.alert('Not yet implemented: "' + data.event + '"');
+        if (!config.useLiveData) {
+            network.view.alert('Not yet implemented: "' + data.event + '"');
+        }
     }
 
     function unknownEvent(data) {
@@ -966,56 +985,62 @@
         return true;
     }
 
-    // TODO: these should be moved out to utility module.
-    function stripPx(s) {
-        return s.replace(/px$/,'');
-    }
-
-    function appendUse(svg, ox, oy, dim, iid, cls) {
-        var use = svg.append('use').attr({
-            transform: translate(ox,oy),
-            'xlink:href': iid,
-            width: dim,
-            height: dim
-        });
-        if (cls) {
-            use.classed(cls, true);
-        }
-        return use;
-    }
-
-    function appendGlyph(svg, ox, oy, dim, iid, cls) {
-        appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
-    }
-
-    function appendBadge(svg, ox, oy, dim, iid, cls) {
-        appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
-    }
-
-    function attachUiBadge(svg) {
-        appendBadge(svg, 12, 50, 30, '#uiAttached', 'uiBadge');
-    }
 
     // ==============================
     // onos instance panel functions
-    var instW = 120;
 
-    function viewBox(w, h) {
-        return '0 0 ' + w + ' ' + h;
+    var instCfg = {
+        rectPad: 8,
+        nodeOx: 9,
+        nodeOy: 9,
+        nodeDim: 40,
+        birdOx: 19,
+        birdOy: 21,
+        birdDim: 21,
+        uiDy: 45,
+        titleDy: 30,
+        textYOff: 20,
+        textYSpc: 15
+    };
+
+    function viewBox(dim) {
+        return '0 0 ' + dim.w + ' ' + dim.h;
+    }
+
+    function instRectAttr(dim) {
+        var pad = instCfg.rectPad;
+        return {
+            x: pad,
+            y: pad,
+            width: dim.w - pad*2,
+            height: dim.h - pad*2,
+            rx: 12
+        };
+    }
+
+    function computeDim(self) {
+        var css = window.getComputedStyle(self);
+        return {
+            w: stripPx(css.width),
+            h: stripPx(css.height)
+        };
     }
 
     function updateInstances() {
         var onoses = oiBox.el.selectAll('.onosInst')
                 .data(onosOrder, function (d) { return d.id; }),
-            boxW = instW * onosOrder.length;
+            instDim = {w:0,h:0},
+            c = instCfg;
 
-        // adjust the width of the panel based on number of instances...
-        oiBox.width(boxW);
+        function nSw(n) {
+            return '# Switches: ' + n;
+        }
 
         // operate on existing onos instances if necessary
         onoses.each(function (d) {
             var el = d3.select(this),
                 svg = el.select('svg');
+            instDim = computeDim(this);
 
             // update online state
             el.classed('online', d.online);
@@ -1026,7 +1051,12 @@
                 attachUiBadge(svg);
             }
 
-            // TODO: update title and property values
+            function updAttr(id, value) {
+                svg.select('text.instLabel.'+id).text(value);
+            }
+
+            updAttr('ip', d.ip);
+            updAttr('ns', nSw(d.switches));
         });
 
 
@@ -1039,61 +1069,64 @@
 
         entering.each(function (d) {
             var el = d3.select(this),
-                css = window.getComputedStyle(this),
-                w = stripPx(css.width),
-                h = stripPx(css.height);
+                rectAttr,
+                svg;
+            instDim = computeDim(this);
+            rectAttr = instRectAttr(instDim);
 
-            var svg = el.append('svg').attr({
-                width: w,
-                height: h,
-                viewBox: viewBox(w, h)
+            svg = el.append('svg').attr({
+                width: instDim.w,
+                height: instDim.h,
+                viewBox: viewBox(instDim)
             });
 
-            svg.append('rect')
-                .attr({
-                    x: 8,
-                    y: 8,
-                    width: 104,
-                    height: 84,
-                    rx: 12
-                });
+            svg.append('rect').attr(rectAttr);
 
-
-            appendGlyph(svg, 9, 9, 36, '#node');
-            appendBadge(svg, 17, 19, 21, '#bird');
+            appendGlyph(svg, c.nodeOx, c.nodeOy, c.nodeDim, '#node');
+            appendBadge(svg, c.birdOx, c.birdOy, c.birdDim, '#bird');
 
             if (d.uiAttached) {
                 attachUiBadge(svg);
             }
 
-            //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);
+            var left = c.nodeOx + c.nodeDim,
+                len = rectAttr.width - left,
+                hlen = len / 2,
+                midline = hlen + left;
 
-            // is the UI attached to this instance?
-            // TODO: need uiAttached boolean in instance data
-            // TODO: use SVG glyph, not png..
-            //if (d.uiAttached) {
-            //if (i === 0) {
-            //    $('<img src="img/ui.png">').attr('class','ui').appendTo(el);
-            //}
+            // title
+            svg.append('text')
+                .attr({
+                    class: 'instTitle',
+                    x: midline,
+                    y: c.titleDy
+                })
+                .text(d.id);
+
+            // a couple of attributes
+            var ty = c.titleDy + c.textYOff;
+
+            function addAttr(id, label) {
+                svg.append('text').attr({
+                    class: 'instLabel ' + id,
+                    x: midline,
+                    y: ty
+                }).text(label);
+                ty += c.textYSpc;
+            }
+
+            addAttr('ip', d.ip);
+            addAttr('ns', nSw(d.switches));
         });
 
         // operate on existing + new onoses here
 
-        // the departed...
-        var exiting = onoses.exit()
-            .transition()
-            .style('opacity', 0)
-            .remove();
+        // adjust the panel size appropriately...
+        oiBox.width(instDim.w * onosOrder.length);
+        oiBox.height(instDim.h);
+
+        // remove any outgoing instances
+        onoses.exit().remove();
     }
 
     function clickInst(d) {
@@ -1128,6 +1161,36 @@
         oiShowMaster = false;
     }
 
+    // TODO: these should be moved out to utility module.
+    function stripPx(s) {
+        return s.replace(/px$/,'');
+    }
+
+    function appendUse(svg, ox, oy, dim, iid, cls) {
+        var use = svg.append('use').attr({
+            transform: translate(ox,oy),
+            'xlink:href': iid,
+            width: dim,
+            height: dim
+        });
+        if (cls) {
+            use.classed(cls, true);
+        }
+        return use;
+    }
+
+    function appendGlyph(svg, ox, oy, dim, iid, cls) {
+        appendUse(svg, ox, oy, dim, iid, cls).classed('glyphIcon', true);
+    }
+
+    function appendBadge(svg, ox, oy, dim, iid, cls) {
+        appendUse(svg, ox, oy, dim, iid,cls ).classed('badgeIcon', true);
+    }
+
+    function attachUiBadge(svg) {
+        appendBadge(svg, 12, instCfg.uiDy, 30, '#uiAttached', 'uiBadge');
+    }
+
     // ==============================
     // force layout modification functions
 
@@ -1763,9 +1826,12 @@
 
     }
 
-    function find(key, array) {
-        for (var idx = 0, n = array.length; idx < n; idx++) {
-            if (array[idx].key === key) {
+    function find(key, array, tag) {
+        var _tag = tag || 'key',
+            idx, n, d;
+        for (idx = 0, n = array.length; idx < n; idx++) {
+            d = array[idx];
+            if (d[_tag] === key) {
                 return idx;
             }
         }