GUI -- Implemented Instance Panel.
- handling addInstance event.

Change-Id: Ic98a3291bd37ecf1155dbe1696167d0635a31972
diff --git a/web/gui/src/main/webapp/app/view/topo/topoInst.js b/web/gui/src/main/webapp/app/view/topo/topoInst.js
new file mode 100644
index 0000000..0d3a2ac
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoInst.js
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Topology Instances Module.
+ Defines modeling of ONOS instances.
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, ps, sus, gs;
+
+    // configuration
+    var instCfg = {
+            rectPad: 8,
+            nodeOx: 9,
+            nodeOy: 9,
+            nodeDim: 40,
+            birdOx: 19,
+            birdOy: 21,
+            birdDim: 21,
+            uiDy: 45,
+            titleDy: 30,
+            textYOff: 20,
+            textYSpc: 15
+        },
+        showLogicErrors = true,
+        idIns = 'topo-p-instance',
+        instOpts = {
+            edge: 'left',
+            width: 20
+        };
+
+    // internal state
+    var onosInstances,
+        onosOrder,
+        oiShowMaster,
+        oiBox;
+
+
+    // ==========================
+    // *** ADD INSTANCE ***
+
+    function addInstance(data) {
+        var id = data.id;
+
+        if (onosInstances[id]) {
+            updateInstance(data);
+            return;
+        }
+        onosInstances[id] = data;
+        onosOrder.push(data);
+        updateInstances();
+    }
+
+    function updateInstance(data) {
+        var id = data.id,
+            d = onosInstances[id];
+        if (d) {
+            angular.extend(d, data);
+            updateInstances();
+        } else {
+            logicError('updateInstance: lookup fail: ID = "' + id + '"');
+        }
+    }
+
+    function computeDim(self) {
+        var css = window.getComputedStyle(self);
+        return {
+            w: sus.stripPx(css.width),
+            h: sus.stripPx(css.height)
+        };
+    }
+
+    function clickInst(d) {
+        var el = d3.select(this),
+            aff = el.classed('affinity');
+        if (!aff) {
+            setAffinity(el, d);
+        } else {
+            cancelAffinity();
+        }
+    }
+
+    function setAffinity(el, d) {
+        d3.selectAll('.onosInst')
+            .classed('mastership', true)
+            .classed('affinity', false);
+        el.classed('affinity', true);
+
+        // TODO: suppress the layers and highlight only specific nodes...
+        //suppressLayers(true);
+        //node.each(function (n) {
+        //    if (n.master === d.id) {
+        //        n.el.classed('suppressed', false);
+        //    }
+        //});
+        oiShowMaster = true;
+    }
+
+    function cancelAffinity() {
+        d3.selectAll('.onosInst')
+            .classed('mastership affinity', false);
+
+        // TODO: restore layer state
+        //restoreLayerState();
+        oiShowMaster = false;
+    }
+
+    function instRectAttr(dim) {
+        var pad = instCfg.rectPad;
+        return {
+            x: pad,
+            y: pad,
+            width: dim.w - pad*2,
+            height: dim.h - pad*2,
+            rx: 6
+        };
+    }
+
+    function viewBox(dim) {
+        return '0 0 ' + dim.w + ' ' + dim.h;
+    }
+
+    function attachUiBadge(svg) {
+        gs.addGlyph(svg, 'uiAttached', 30, true, [12, instCfg.uiDy])
+            .classed('badgeIcon uiBadge', true);
+    }
+
+    function instColor(id, online) {
+        // TODO: fix this..
+        //return cat7.get(id, !online, network.view.getTheme());
+        return 'blue';
+    }
+
+    // ==============================
+
+    function updateInstances() {
+        var onoses = oiBox.el().selectAll('.onosInst')
+                .data(onosOrder, function (d) { return d.id; }),
+            instDim = {w:0,h:0},
+            c = instCfg;
+
+        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);
+
+            // update ui-attached state
+            svg.select('use.uiBadge').remove();
+            if (d.uiAttached) {
+                attachUiBadge(svg);
+            }
+
+            function updAttr(id, value) {
+                svg.select('text.instLabel.'+id).text(value);
+            }
+
+            updAttr('ip', d.ip);
+            updAttr('ns', nSw(d.switches));
+        });
+
+
+        // operate on new onos instances
+        var entering = onoses.enter()
+            .append('div')
+            .attr('class', 'onosInst')
+            .classed('online', function (d) { return d.online; })
+            .on('click', clickInst);
+
+        entering.each(function (d) {
+            var el = d3.select(this),
+                rectAttr,
+                svg;
+            instDim = computeDim(this);
+            rectAttr = instRectAttr(instDim);
+
+            svg = el.append('svg').attr({
+                width: instDim.w,
+                height: instDim.h,
+                viewBox: viewBox(instDim)
+            });
+
+            svg.append('rect').attr(rectAttr);
+
+            gs.addGlyph(svg, 'bird', 28, true, [14, 14])
+                .classed('badgeIcon', true);
+
+            if (d.uiAttached) {
+                attachUiBadge(svg);
+            }
+
+            var left = c.nodeOx + c.nodeDim,
+                len = rectAttr.width - left,
+                hlen = len / 2,
+                midline = hlen + left;
+
+            // 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
+        // set the affinity colors...
+        onoses.each(function (d) {
+            var el = d3.select(this),
+                rect = el.select('svg').select('rect'),
+                col = instColor(d.id, d.online);
+            rect.style('fill', col);
+        });
+
+        // adjust the panel size appropriately...
+        oiBox.width(instDim.w * onosOrder.length);
+        oiBox.height(instDim.h);
+
+        // remove any outgoing instances
+        onoses.exit().remove();
+    }
+
+
+    // ==========================
+
+    function logicError(msg) {
+        if (showLogicErrors) {
+            $log.warn('TopoInstService: ' + msg);
+        }
+    }
+
+    function initInst() {
+        oiBox = ps.createPanel(idIns, instOpts);
+        oiBox.show();
+
+        onosInstances = {};
+        onosOrder = [];
+        oiShowMaster = false;
+    }
+
+    function destroyInst() {
+        ps.destroyPanel(idIns);
+        oiBox = null;
+    }
+
+    // ==========================
+
+    angular.module('ovTopo')
+    .factory('TopoInstService',
+        ['$log', 'PanelService', 'SvgUtilService', 'GlyphService',
+
+        function (_$log_, _ps_, _sus_, _gs_) {
+            $log = _$log_;
+            ps = _ps_;
+            sus = _sus_;
+            gs = _gs_;
+
+            return {
+                initInst: initInst,
+                destroyInst: destroyInst,
+                addInstance: addInstance
+            };
+        }]);
+}());