GUI -- implemented ONOS instance affinity display.
- augmented ESC key handling to cancel affinity display before deslecting nodes.
- augmented setRadioButtons to return buttonset api, so we can query what is currently selected.

Change-Id: I17532bae7ea5fa639ce5d600c67e6c44728ff67f
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 87aa63d..20c679a 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -347,7 +347,8 @@
 
         function setRadioButtons(vid, btnSet) {
             var view = views[vid],
-                btnG;
+                btnG,
+                api = {};
 
             // lazily create the buttons...
             if (!(btnG = view.radioButtons)) {
@@ -365,10 +366,12 @@
                         })
                         .text(txt);
 
+                    btn.id = bid;
                     btnG.buttonDef[uid] = btn;
 
                     if (i === 0) {
                         button.classed('active', true);
+                        btnG.selected = bid;
                     }
                 });
 
@@ -382,6 +385,7 @@
                         if (!act) {
                             btnG.selectAll('span').classed('active', false);
                             button.classed('active', true);
+                            btnG.selected = btn.id;
                             if (isF(btn.cb)) {
                                 btn.cb(view.token(), btn);
                             }
@@ -389,10 +393,16 @@
                     });
 
                 view.radioButtons = btnG;
+
+                api.selected = function () {
+                    return btnG.selected;
+                }
             }
 
             // attach the buttons to the masthead
             $mastRadio.node().appendChild(btnG.node());
+            // return an api for interacting with the button set
+            return api;
         }
 
         function setupGlobalKeys() {
@@ -662,7 +672,7 @@
             },
 
             setRadio: function (btnSet) {
-                setRadioButtons(this.vid, btnSet);
+                return setRadioButtons(this.vid, btnSet);
             },
 
             setKeys: function (keyArg) {
diff --git a/web/gui/src/main/webapp/topo2.css b/web/gui/src/main/webapp/topo2.css
index f485e1e..94e6477 100644
--- a/web/gui/src/main/webapp/topo2.css
+++ b/web/gui/src/main/webapp/topo2.css
@@ -225,8 +225,15 @@
     border: 2px solid #555;
 }
 
-#topo svg .suppressed,
-#topo-oibox .suppressed {
+#topo-oibox .onosInst.mastership {
+    opacity: 0.3;
+}
+#topo-oibox .onosInst.mastership.affinity {
+    opacity: 1.0;
+}
+
+
+#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 e14f020..3a5bacc 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -112,11 +112,17 @@
     };
 
     // radio buttons
-    var btnSet = [
-        { text: 'All Layers', cb: showAllLayers },
-        { text: 'Packet Only', cb: showPacketLayer },
-        { text: 'Optical Only', cb: showOpticalLayer }
-    ];
+    var layerButtons = [
+            { text: 'All Layers', id: 'all', cb: showAllLayers },
+            { text: 'Packet Only', id: 'pkt', cb: showPacketLayer },
+            { text: 'Optical Only', id: 'opt', cb: showOpticalLayer }
+        ],
+        layerBtnSet,
+        layerBtnDispatch = {
+            all: showAllLayers,
+            pkt: showPacketLayer,
+            opt: showOpticalLayer
+        };
 
     // key bindings
     var keyDispatch = {
@@ -129,7 +135,7 @@
         P: togglePorts,
         U: unpin,
         R: resetZoomPan,
-        esc: deselectAll
+        esc: handleEscape
     };
 
     // state variables
@@ -163,8 +169,8 @@
         onosInstances = {},
         onosOrder = [],
         oiBox,
+        oiShowMaster = false,
 
-        viewMode = 'showAll',
         portLabelsOn = false;
 
     // D3 selections
@@ -311,6 +317,14 @@
         }
     }
 
+    function handleEscape(view) {
+        if (oiShowMaster) {
+            cancelAffinity();
+        } else {
+            deselectAll();
+        }
+    }
+
     // ==============================
     // Radio Button Callbacks
 
@@ -352,13 +366,17 @@
         });
     }
 
-    function showAllLayers() {
-        node.classed('suppressed', false);
-        link.classed('suppressed', false);
+    function suppressLayers(b) {
+        node.classed('suppressed', b);
+        link.classed('suppressed', b);
 //        d3.selectAll('svg .port').classed('inactive', false);
 //        d3.selectAll('svg .portText').classed('inactive', false);
     }
 
+    function showAllLayers() {
+        suppressLayers(false);
+    }
+
     function showPacketLayer() {
         node.classed('suppressed', true);
         link.classed('suppressed', true);
@@ -371,6 +389,10 @@
         unsuppressLayer('opt');
     }
 
+    function restoreLayerState() {
+        layerBtnDispatch[layerBtnSet.selected()]();
+    }
+
     // ==============================
     // Private functions
 
@@ -674,6 +696,7 @@
             .append('div')
             .attr('class', 'onosInst')
             .classed('online', function (d) { return d.online; })
+            .on('click', clickInst)
             .text(function (d) { return d.id; });
 
         // operate on existing + new onoses here
@@ -685,6 +708,38 @@
             .remove();
     }
 
+    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);
+
+        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);
+        restoreLayerState();
+        oiShowMaster = false;
+    }
+
     // ==============================
     // force layout modification functions
 
@@ -1682,7 +1737,7 @@
         }
 
         // set our radio buttons and key bindings
-        view.setRadio(btnSet);
+        layerBtnSet = view.setRadio(layerButtons);
         view.setKeys(keyDispatch);
 
         // patch in our "button bar" for now