GUI -- [ONOS-347] -  Suppress display of offline-devices (press the 'M' key).
 - also minor cleanup of link "online" state, based on src/tgt nodes online state.

Change-Id: I6d4c634e4d9f7f994f9871b7c95e3264d6ada2bb
diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_14_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_14_onos.json
new file mode 100644
index 0000000..9df9d61
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/simple/ev_14_onos.json
@@ -0,0 +1,22 @@
+{
+  "event": "removeDevice",
+  "payload": {
+    "id": "of:0000ffffffff0003",
+    "type": "switch",
+    "online": false,
+    "location": {
+      "type": "latlng",
+      "lat": 40.7127,
+      "lng": -74.0059
+    },
+    "labels": [
+      "",
+      "sw-3",
+      "0000ffffffff0003"
+    ],
+    "metaUi": {
+      "x": 800,
+      "y": 280
+    }
+  }
+}
diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json
index 6f390b5..755255a 100644
--- a/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json
+++ b/web/gui/src/main/webapp/json/ev/simple/ev_5_onos.json
@@ -3,6 +3,7 @@
   "payload": {
     "id": "of:0000ffffffff0003/21-of:0000ffffffff0008/20",
     "type": "direct",
+    "online": true,
     "linkWidth": 2,
     "src": "of:0000ffffffff0003",
     "srcPort": "21",
diff --git a/web/gui/src/main/webapp/json/ev/simple/scenario.json b/web/gui/src/main/webapp/json/ev/simple/scenario.json
index 4f2ae60..524377c 100644
--- a/web/gui/src/main/webapp/json/ev/simple/scenario.json
+++ b/web/gui/src/main/webapp/json/ev/simple/scenario.json
@@ -24,6 +24,7 @@
     "10. update link (increase width, update props)",
     "11. update link (reduce width, update props)",
     "12. remove link",
-    "13. remove host (10.0.0.17)"
+    "13. remove host (10.0.0.17)",
+    "13. remove device [3]"
   ]
 }
diff --git a/web/gui/src/main/webapp/topo.js b/web/gui/src/main/webapp/topo.js
index 5b46e3d..1620048 100644
--- a/web/gui/src/main/webapp/topo.js
+++ b/web/gui/src/main/webapp/topo.js
@@ -145,6 +145,7 @@
         D: [toggleDetails, 'Disable / enable details pane'],
         B: [toggleBg, 'Toggle background image'],
         H: [toggleHosts, 'Toggle host visibility'],
+        M: [toggleOffline, 'Toggle offline visibility'],
         L: [cycleLabels, 'Cycle device labels'],
         P: togglePorts,
         U: [unpin, 'Unpin node (hover mouse over)'],
@@ -203,6 +204,7 @@
         cat7 = d3u.cat7(),
         colorAffinity = false,
         showHosts = false,
+        showOffline = true,
         useDetails = true,
         haveDetails = false;
 
@@ -330,6 +332,12 @@
         flash('Hosts ' + visVal(showHosts));
     }
 
+    function toggleOffline() {
+        showOffline = !showOffline;
+        updateOfflineVisibility();
+        flash('Offline devices ' + visVal(showOffline));
+    }
+
     function cycleLabels() {
         deviceLabelIndex = (deviceLabelIndex === 2)
             ? 0 : deviceLabelIndex + 1;
@@ -711,13 +719,20 @@
         evTrace(data);
         var device = data.payload,
             id = device.id,
-            d = network.lookup[id];
+            d = network.lookup[id],
+            wasOnline;
+
         if (d) {
+            wasOnline = d.online;
             $.extend(d, device);
             if (positionNode(d, true)) {
                 sendUpdateMeta(d, true);
             }
             updateNodes();
+            if (wasOnline !== d.online) {
+                findAttachedLinks(d.id).forEach(restyleLinkElement);
+                updateOfflineVisibility(d);
+            }
         } else {
             logicError('updateDevice lookup fail. ID = "' + id + '"');
         }
@@ -742,7 +757,10 @@
             d = network.lookup[id];
         if (d) {
             $.extend(d, host);
-            updateHostState(d);
+            if (positionNode(d, true)) {
+                sendUpdateMeta(d, true);
+            }
+            updateNodes(d);
         } else {
             logicError('updateHost lookup fail. ID = "' + id + '"');
         }
@@ -1339,8 +1357,10 @@
             class: 'link',
 
             type: function () { return 'hostLink'; },
-            // TODO: ideally, we should see if our edge switch is online...
-            online: function () { return true; },
+            online: function () {
+                // hostlink target is edge switch
+                return lnk.target.online;
+            },
             linkWidth: function () { return 1; }
         });
         return lnk;
@@ -1366,8 +1386,9 @@
             },
             online: function () {
                 var s = lnk.fromSource,
-                    t = lnk.fromTarget;
-                return (s && s.online) || (t && t.online);
+                    t = lnk.fromTarget,
+                    both = lnk.source.online && lnk.target.online;
+                return both && ((s && s.online) || (t && t.online));
             },
             linkWidth: function () {
                 var s = lnk.fromSource,
@@ -1435,12 +1456,12 @@
 
         // operate on exiting links:
         link.exit()
-            .attr('stroke-dasharray', '3, 3')
+            .attr('stroke-dasharray', '3 3')
             .style('opacity', 0.5)
             .transition()
             .duration(1500)
             .attr({
-                'stroke-dasharray': '3, 12',
+                'stroke-dasharray': '3 12',
                 stroke: config.topo.linkOutColor,
                 'stroke-width': config.topo.linkOutWidth
             })
@@ -1575,8 +1596,6 @@
             node.fixed = true;
             node.px = node.x = x;
             node.py = node.y = y;
-            //node.px = x;
-            //node.py = y;
             return;
         }
 
@@ -1586,8 +1605,6 @@
             node.fixed = true;
             node.px = node.x = coord[0];
             node.py = node.y = coord[1];
-            //node.x = coord[0];
-            //node.y = coord[1];
             return true;
         }
 
@@ -1726,15 +1743,8 @@
     }
 
     function updateHostLabel(d) {
-        var label = hostLabel(d),
-            host = d.el;
-
-        host.select('text').text(label);
-    }
-
-    // FIXME : fold this into updateNodes.
-    function updateHostState(hostData) {
-        updateHostLabel(hostData);
+        var label = trimLabel(hostLabel(d));
+        d.el.select('text').text(label);
     }
 
     function updateHostVisibility() {
@@ -1743,6 +1753,53 @@
         linkG.selectAll('.hostLink').style('visibility', v);
     }
 
+    function findOfflineNodes() {
+        var a = [];
+        network.nodes.forEach(function (d) {
+            if (d.class === 'device' && !d.online) {
+                a.push(d);
+            }
+        });
+        return a;
+    }
+
+    function updateOfflineVisibility(dev) {
+        var so = showOffline,
+            sh = showHosts,
+            vb = 'visibility',
+            v, off, al, ah, db, b;
+
+        function updAtt(show) {
+            al.forEach(function (d) {
+                b = show && ((d.type() !== 'hostLink') || sh);
+                d.el.style(vb, visVal(b));
+            });
+            ah.forEach(function (d) {
+                b = show && sh;
+                d.el.style(vb, visVal(b));
+            });
+        }
+
+        if (dev) {
+            // updating a specific device that just toggled off/on-line
+            db = dev.online || so;
+            dev.el.style(vb, visVal(db));
+            al = findAttachedLinks(dev.id);
+            ah = findAttachedHosts(dev.id);
+            updAtt(db);
+        } else {
+            // updating all offline devices
+            v = visVal(so);
+            off = findOfflineNodes();
+            off.forEach(function (d) {
+                d.el.style(vb, v);
+                al = findAttachedLinks(d.id);
+                ah = findAttachedHosts(d.id);
+                updAtt(so);
+            });
+        }
+    }
+
     function nodeMouseOver(d) {
         hovered = d;
         requestTrafficForMode();
@@ -1779,8 +1836,8 @@
         });
 
         node.filter('.host').each(function (d) {
-            var node = d.el;
-            // TODO: appropriate update of host visuals
+            updateHostLabel(d);
+            positionNode(d, true);
         });
 
         // operate on entering nodes: