ONOS-6327: Implement details panel for host view.
ONOS-6326: Add friendly names to hosts.
- PLENTY more YakShaving:
  * some cleanup of the device view handler
  * introduce navPath field to PropertyPanel
  * introduce "-" name annotation to represent "use default"
  * (and more...)

Change-Id: I2afc0f1f29c726b90e97e492527edde2d1345ece
diff --git a/web/gui/src/main/webapp/app/common.css b/web/gui/src/main/webapp/app/common.css
index b8952c3..7aed6ef 100644
--- a/web/gui/src/main/webapp/app/common.css
+++ b/web/gui/src/main/webapp/app/common.css
@@ -22,3 +22,18 @@
     cursor: pointer;
 }
 
+.light .editable {
+    border-bottom: 1px dashed #ca504b;
+}
+
+.dark .editable {
+    border-bottom: 1px dashed #df4f4a;
+}
+
+.light svg.embeddedIcon .icon .glyph {
+    fill: #0071bd;
+}
+
+.dark svg.embeddedIcon .icon .glyph {
+    fill: #375b7f;
+}
diff --git a/web/gui/src/main/webapp/app/view/device/device-theme.css b/web/gui/src/main/webapp/app/view/device/device-theme.css
index 3aa67d2..f7e2873 100644
--- a/web/gui/src/main/webapp/app/view/device/device-theme.css
+++ b/web/gui/src/main/webapp/app/view/device/device-theme.css
@@ -18,15 +18,6 @@
  ONOS GUI -- Device View (theme) -- CSS file
  */
 
-
-.light .dev-icon svg.embeddedIcon .icon .glyph {
-    fill: #0071bd;
-}
-
-.light #device-details-panel .editable {
-    border-bottom: 1px dashed #ca504b;
-}
-
 .light #device-details-panel .bottom th {
     background-color: #e5e5e6;
 }
@@ -38,12 +29,3 @@
     background-color: #f4f4f4;
 }
 
-/* ========== DARK Theme ========== */
-
-.dark .dev-icon svg.embeddedIcon .icon .glyph {
-    fill: #375b7f;
-}
-
-.dark #device-details-panel .editable {
-    border-bottom: 1px dashed #df4f4a;
-}
diff --git a/web/gui/src/main/webapp/app/view/host/host.css b/web/gui/src/main/webapp/app/view/host/host.css
index 377918a..1aa6f86 100644
--- a/web/gui/src/main/webapp/app/view/host/host.css
+++ b/web/gui/src/main/webapp/app/view/host/host.css
@@ -58,7 +58,7 @@
 
 #host-details-panel h2 input {
     font-size: 0.90em;
-    width: 106%;
+    width: 112%;
 }
 
 #host-details-panel .top-tables {
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index ee23f00..4e8153c 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -29,7 +29,6 @@
         pStartY,
         pHeight,
         top,
-        bottom,
         iconDiv,
         wSize,
         editingName = false,
@@ -37,15 +36,19 @@
 
     // constants
     var topPdg = 28,
-        ctnrPdg = 24,
-        scrollSize = 17,
-
         pName = 'host-details-panel',
         detailsReq = 'hostDetailsRequest',
         detailsResp = 'hostDetailsResponse',
         nameChangeReq = 'hostNameChangeRequest',
         nameChangeResp = 'hostNameChangeResponse';
 
+    var propOrder = [
+            'id', 'ip', 'mac', 'vlan', 'configured', 'location'
+        ],
+        friendlyProps = [
+            'Host ID', 'IP Address', 'MAC Address', 'VLAN',
+            'Configured', 'Location'
+        ];
 
     function closePanel() {
         if (detailsPanel.isVisible()) {
@@ -71,12 +74,13 @@
     function editNameSave() {
         var nameH2 = top.select('h2'),
             id = $scope.panelData.id,
+            ip = $scope.panelData.ip,
             val,
             newVal;
 
         if (editingName) {
             val = nameH2.select('input').property('value').trim();
-            newVal = val || id;
+            newVal = val || ip;
 
             exitEditMode(nameH2, newVal);
             $scope.panelData.name = newVal;
@@ -115,7 +119,7 @@
     }
 
     function setUpPanel() {
-        var container, closeBtn, tblDiv;
+        var container, closeBtn;
         detailsPanel.empty();
 
         container = detailsPanel.append('div').classed('container', true);
@@ -126,22 +130,29 @@
         iconDiv = top.append('div').classed('host-icon', true);
         top.append('h2').classed('editable clickable', true).on('click', editName);
 
-        // tblDiv = top.append('div').classed('top-tables', true);
-        // tblDiv.append('div').classed('left', true).append('table');
-        // tblDiv.append('div').classed('right', true).append('table');
-
+        top.append('div').classed('top-tables', true);
         top.append('hr');
+    }
 
-        // bottom = container.append('div').classed('bottom', true);
-        // bottom.append('h2').classed('ports-title', true).text('Ports');
-        // bottom.append('table');
+    function addProp(tbody, index, value) {
+        var tr = tbody.append('tr');
+
+        function addCell(cls, txt) {
+            tr.append('td').attr('class', cls).text(txt);
+        }
+        addCell('label', friendlyProps[index] + ' :');
+        addCell('value', value);
     }
 
     function populateTop(details) {
+        var tab = top.select('.top-tables').append('tbody');
+
         is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40);
         top.select('h2').text(details.name);
 
-        // TODO: still need to add host properties (one per line)
+        propOrder.forEach(function (prop, i) {
+            addProp(tab, i, details[prop]);
+        });
     }
 
     function populateDetails(details) {
@@ -149,7 +160,7 @@
         populateTop(details);
         detailsPanel.height(pHeight);
         // configure width based on content.. for now hardcoded
-        detailsPanel.width(600);
+        detailsPanel.width(400);
     }
 
     function respDetailsCb(data) {
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 8acebcc..29f149f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -61,6 +61,7 @@
             Z: [tos.toggleOblique, 'Toggle oblique view (Experimental)'],
             N: [fltr.clickAction, 'Cycle node layers'],
             L: [tfs.cycleDeviceLabels, 'Cycle device labels'],
+            'shift-L': [tfs.cycleHostLabels, 'Cycle host labels'],
             U: [tfs.unpin, 'Unpin node (hover mouse over)'],
             R: [resetZoom, 'Reset pan / zoom'],
             dot: [ttbs.toggleToolbar, 'Toggle Toolbar'],
@@ -83,7 +84,7 @@
 
             _helpFormat: [
                 ['I', 'O', 'D', 'H', 'M', 'P', 'dash', 'B', 'G', 'S' ],
-                ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'E', '-', 'dot'],
+                ['X', 'Z', 'N', 'L', 'shift-L', 'U', 'R', '-', 'E', '-', 'dot'],
                 []   // this column reserved for overlay actions
             ]
         };
@@ -494,6 +495,7 @@
         toggleMap(prefsState.bg);
         toggleSprites(prefsState.spr);
         t3s.setDevLabIndex(prefsState.dlbls);
+        t3s.setHostLabIndex(prefsState.hlbls);
         flash.enable(true);
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoD3.js b/web/gui/src/main/webapp/app/view/topo/topoD3.js
index e45f491..3d232ee 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoD3.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoD3.js
@@ -139,6 +139,22 @@
         ps.setPrefs('topo_prefs', p);
     }
 
+    function incHostLabIndex() {
+        setHostLabIndex(hostLabelIndex+1);
+        switch(hostLabelIndex) {
+            case 0: return 'Show friendly host labels';
+            case 1: return 'Show host IP Addresses';
+            case 2: return 'Show host MAC Addresses';
+        }
+    }
+
+    function setHostLabIndex(mode) {
+        hostLabelIndex = mode % 3;
+        var p = ps.getPrefs('topo_prefs', ttbs.defaultPrefs);
+        p.hlbls = hostLabelIndex;
+        ps.setPrefs('topo_prefs', p);
+    }
+
     function hostLabel(d) {
         var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
         return d.labels[idx];
@@ -617,6 +633,8 @@
 
                 incDevLabIndex: incDevLabIndex,
                 setDevLabIndex: setDevLabIndex,
+                incHostLabIndex: incHostLabIndex,
+                setHostLabIndex: setHostLabIndex,
                 hostLabel: hostLabel,
                 deviceLabel: deviceLabel,
                 trimLabel: trimLabel,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 9b9a29c..f38fa56 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -516,6 +516,13 @@
         });
     }
 
+    function cycleHostLabels() {
+        flash.flash(td3.incHostLabIndex());
+        tms.findHosts().forEach(function (d) {
+            td3.updateHostLabel(d);
+        });
+    }
+
     function unpin() {
         var hov = tss.hovered();
         if (hov) {
@@ -1240,6 +1247,7 @@
                 togglePorts: tls.togglePorts,
                 toggleOffline: toggleOffline,
                 cycleDeviceLabels: cycleDeviceLabels,
+                cycleHostLabels: cycleHostLabels,
                 unpin: unpin,
                 showMastership: showMastership,
                 showBadLinks: showBadLinks,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoModel.js b/web/gui/src/main/webapp/app/view/topo/topoModel.js
index 3236ae5..e841907 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoModel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoModel.js
@@ -366,6 +366,16 @@
         return a;
     }
 
+    function findHosts() {
+        var hosts = [];
+        nodes.forEach(function (d) {
+            if (d.class === 'host') {
+                hosts.push(d);
+            }
+        });
+        return hosts;
+    }
+
     function findAttachedHosts(devId) {
         var hosts = [];
         nodes.forEach(function (d) {
@@ -453,6 +463,7 @@
                 findLink: findLink,
                 findLinkById: findLinkById,
                 findDevices: findDevices,
+                findHosts: findHosts,
                 findAttachedHosts: findAttachedHosts,
                 findAttachedLinks: findAttachedLinks,
                 findBadLinks: findBadLinks
diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
index 100cd96..8f8eef5 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -35,8 +35,7 @@
         sumMax = 226,           // summary panel max height
         padTop = 16,            // summary panel padding below masthead
         padding = 16,           // panel internal padding
-        padFudge = padTop + 2 * padding,
-        devPath = 'device';
+        padFudge = padTop + 2 * padding;
 
     // internal state
     var useDetails = true,      // should we show details if we have 'em?
@@ -230,10 +229,9 @@
     // === -----------------------------------------------------
     //  Functions for populating the detail panel
 
-    var isDevice = {
-        switch: 1,
-        roadm: 1,
-        otn:1
+    var navPathIdKey = {
+        device: 'devId',
+        host: 'hostId'
     };
 
     function displaySingle(data) {
@@ -246,15 +244,19 @@
                 .classed('clickable', true),
             table = detail.appendBody('table'),
             tbody = table.append('tbody'),
-            navFn;
+            navFn,
+            navPath;
 
         gs.addGlyph(svg, (data.type || 'unknown'), 26);
         title.text(data.title);
 
-        // only add navigation when displaying a device
-        if (isDevice[data.type]) {
+        // add navigation hot-link if defined
+        navPath = data.navPath;
+        if (navPath) {
             navFn = function () {
-                ns.navTo(devPath, { devId: data.id });
+                var arg = {};
+                arg[navPathIdKey[navPath]] = data.id;
+                ns.navTo(navPath, arg);
             };
 
             svg.on('click', navFn);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index 351154c..82d14ac 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -75,6 +75,7 @@
             hosts: 0,
             offdev: 1,
             dlbls: 0,
+            hlbls: 0,
             porthl: 1,
             bg: 0,
             spr: 0,
@@ -278,7 +279,7 @@
         toolbar.toggle();
         persistTopoPrefs('toolbar');
     }
-    
+
     function selectOverlay(ovid) {
         var idx = ovIndex[defaultOverlay] || 0,
             pidx = (ovid === null) ? 0 : ovIndex[ovid] || -1;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js b/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
index ef3698e..9d822c8 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Prefs.js
@@ -26,6 +26,7 @@
         hosts: 0,
         offdev: 1,
         dlbls: 0,
+        hlbls: 0,
         porthl: 1,
         bg: 0,
         spr: 0,
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
index f0a2914..d14e9ab 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
@@ -41,8 +41,8 @@
 
             'updateDeviceColors', 'toggleHosts',
             'togglePorts', 'toggleOffline',
-            'cycleDeviceLabels', 'unpin', 'showMastership', 'showBadLinks',
-            'setNodeScale',
+            'cycleDeviceLabels', 'cycleHostLabels', 'unpin',
+            'showMastership', 'showBadLinks', 'setNodeScale',
 
             'resetAllLocations', 'addDevice', 'updateDevice', 'removeDevice',
             'addHost', 'updateHost', 'moveHost', 'removeHost',
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
index d1db42a..dd32491 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
@@ -210,10 +210,11 @@
     it('should define api functions', function () {
         expect(fs.areFunctions(tms, [
             'initModel', 'newDim', 'destroyModel',
-            'positionNode', 'resetAllLocations', 'createDeviceNode', 'createHostNode',
+            'positionNode', 'resetAllLocations',
+            'createDeviceNode', 'createHostNode',
             'createHostLink', 'createLink',
             'coordFromLngLat', 'lngLatFromCoord',
-            'findLink', 'findLinkById', 'findDevices',
+            'findLink', 'findLinkById', 'findDevices', 'findHosts',
             'findAttachedHosts', 'findAttachedLinks', 'findBadLinks'
         ])).toBeTruthy();
     });