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/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;
             }
         }