ONOS-6730: Topo View i18n:
- Deprecate non-localized PropertyPanel.addProp() methods.
- Add modify*LinkDetails() methods to UiTopoOverlay class.
- Augment TVMH.RequestDetails to handle link details requests.
- Refactor deviceDetails() to allow piecemeal construction of the Properties Panel.
    This allows us to include (or not) the location properties (geo/grid).
- Refactor hostDetails() for piecemeal construction of Properties Panel.
- Add edgeLinkDetails() and infraLinkDetails() methods.
- No lat/long suppression now done server-side. Check for trailing separator.
- Augment requestDetails() to format link details requests.
- Added lion.getSafe(Enum<?>) method.
- Added DeviceEnums and LinkEnums resource bundles.

Change-Id: Ibbd113a7d5ef73765cd10aed0fb7ea8efbaa16c5
diff --git a/web/gui/src/main/webapp/app/view/topo/topo-theme.css b/web/gui/src/main/webapp/app/view/topo/topo-theme.css
index a19e6e8..5f8f8d5 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo-theme.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo-theme.css
@@ -45,11 +45,11 @@
 /* --- general topo-panel styling --- */
 
 .topo-p svg {
-    background: #c0242b;
+    background: none;
 }
 
 .topo-p svg .glyph {
-    fill: #ffffff;
+    fill: #c0242b;
 }
 
 .topo-p hr {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoLink.js b/web/gui/src/main/webapp/app/view/topo/topoLink.js
index ed06ce1..d4f1dfe 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoLink.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoLink.js
@@ -254,8 +254,9 @@
         d.el.classed('selected', true);
         selectedLinks[d.key] = { key: d };
 
-        tps.displayLink(d, tov.hooks.modifyLinkData);
-        tps.displaySomething();
+        // TODO: deprecate tov.hooks.modifyLinkData
+        // tps.displayLink(d, tov.hooks.modifyLinkData);
+        // tps.displaySomething();
     }
 
     // ====== MOUSE EVENT HANDLERS ======
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 eac6a89..3452614 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -156,8 +156,10 @@
         tbody.append('tr').append('td').attr('colspan', 2).append('hr');
     }
 
-    function addBtnFooter() {
-        detail.appendFooter('hr');
+    function addBtnFooter(sepAlreadyThere) {
+        if (!sepAlreadyThere) {
+            detail.appendFooter('hr');
+        }
         detail.appendFooter('div').classed('actionBtns', true);
     }
 
@@ -173,35 +175,25 @@
         function addCell(cls, txt) {
             tr.append('td').attr('class', cls).text(txt);
         }
+
         addCell('label', lab + ' :');
         addCell('value', value);
     }
 
     function listProps(tbody, data) {
+        var sepLast = false;
 
-        // Suppress Lat Long in details panel if null
-        if (data.propLabels.latitude === null ||
-            data.propLabels.longitude === null) {
-            var idx = data.propOrder.indexOf('latitude');
-            data.propOrder.splice(idx, 3);
-        }
-
+        // note: track whether we end with a separator or not...
         data.propOrder.forEach(function (p) {
-            // TODO: remove after topo view fully i18n'd
-            var foo = data.props && data.props[p];
-
             if (p === '-') {
                 addSep(tbody);
-
+                sepLast = true;
             } else {
-                // TODO: remove this if/else once DETAILS panel fixed for i18n
-                if (foo !== undefined) {
-                    addProp(tbody, p, foo);
-                } else {
-                    addProp(tbody, data.propLabels[p], data.propValues[p]);
-                }
+                addProp(tbody, data.propLabels[p], data.propValues[p]);
+                sepLast = false;
             }
         });
+        return sepLast;
     }
 
     function watchWindow() {
@@ -232,9 +224,10 @@
                 .append('svg'),
             title = summary.appendHeader('h2'),
             table = summary.appendBody('table'),
-            tbody = table.append('tbody');
+            tbody = table.append('tbody'),
+            glyphId = data.glyphId || 'bird';
 
-        gs.addGlyph(svg, 'bird', 24, 0, [1, 1]);
+        gs.addGlyph(svg, glyphId, 24, 0, [1, 1]);
 
         title.text(data.title);
         listProps(tbody, data);
@@ -249,8 +242,13 @@
     };
 
     function displaySingle(data) {
+        var sepLast;
+
         detail.setup();
 
+        // TODO: remove
+        $log.debug('>> Display Single Item Details', data);
+
         var svg = detail.appendHeader('div')
                 .classed('icon clickable', true)
                 .append('svg'),
@@ -261,7 +259,7 @@
             navFn,
             navPath;
 
-        gs.addGlyph(svg, (data.type || 'unknown'), 26);
+        gs.addGlyph(svg, (data.glyphId || 'm_unknown'), 26);
         title.text(data.title);
 
         // add navigation hot-link if defined
@@ -277,8 +275,8 @@
             title.on('click', navFn);
         }
 
-        listProps(tbody, data);
-        addBtnFooter();
+        sepLast = listProps(tbody, data);
+        addBtnFooter(sepLast);
     }
 
     function displayMulti(ids) {
@@ -288,9 +286,9 @@
             table = detail.appendBody('table'),
             tbody = table.append('tbody');
 
-        title.text('Selected Items');
+        title.text(topoLion('title_selected_items'));
         ids.forEach(function (d, i) {
-            addProp(tbody, i+1, d);
+            addProp(tbody, i + 1, d);
         });
         addBtnFooter();
     }
@@ -331,6 +329,7 @@
         return d.expected();
     }
 
+    // TODO: implement server-side processing of link details
     var coreOrder = [
             'Type', 'Expected', '-',
             'A_type', 'A_id', 'A_label', 'A_port', '-',
@@ -342,6 +341,7 @@
             'B_type', 'B_id', 'B_label', 'B_port',
         ];
 
+    // FIXME: DEPRECATED (no longer called)
     function displayLink(data, modifyCb) {
         detail.setup();
 
@@ -428,6 +428,7 @@
             summary.panel().show();
             summary.adjustHeight(sumFromTop, sumMax);
         }
+
         if (detail.panel().isVisible()) {
             detail.down(_show);
         } else {
@@ -493,7 +494,7 @@
             verb;
 
         useDetails = kev ? !useDetails : !!x;
-        verb = useDetails ? 'Enable' : 'Disable';
+        verb = useDetails ? 'Enable' : 'Disable'; // TODO: Lion
 
         if (useDetails) {
             if (haveDetails) {
@@ -502,7 +503,7 @@
         } else {
             hideDetailPanel();
         }
-        flash.flash(verb + ' details panel');
+        flash.flash(verb + ' details panel'); // TODO: Lion
         return useDetails;
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index 5d23f1e..e9829e0 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // injected refs
-    var fs, wss, tov, tps, tts, sus;
+    var $log, fs, wss, tov, tps, tts, sus;
 
     // api to topoForce
     var api;
@@ -183,10 +183,31 @@
     // === -----------------------------------------------------
 
     function requestDetails(data) {
-        wss.sendEvent('requestDetails', {
-            id: data.id,
-            class: data.class,
-        });
+        var itemClass = data.class,
+            payload = {
+                class: itemClass,
+                id: data.id,
+            };
+
+        // special handling for links...
+        if (itemClass === 'link') {
+            payload.key = data.key;
+            if (data.source.class === 'host') {
+                payload.isEdgeLink = true;
+                payload.sourceId = data.source.id;
+                payload.targetId = data.source.cp.device;
+                payload.targetPort = data.source.cp.port;
+            } else {
+                payload.isEdgeLink = false;
+                payload.sourceId = data.source.id;
+                payload.sourcePort = data.srcPort;
+                payload.targetId = data.target.id;
+                payload.targetPort = data.tgtPort;
+            }
+        }
+
+        $log.debug('EVENT> requestDetails', payload);
+        wss.sendEvent('requestDetails', payload);
     }
 
     // === -----------------------------------------------------
@@ -210,10 +231,7 @@
     function singleSelect() {
         var data = getSel(0).obj;
 
-        // the link details are already taken care of in topoLink.js
-        if (data.class === 'link') {
-            return;
-        }
+        $log.debug('Requesting details from server for', data);
         requestDetails(data);
         // NOTE: detail panel is shown as a response to receiving
         //       a 'showDetails' event from the server. See 'showDetails'
@@ -304,10 +322,11 @@
 
     angular.module('ovTopo')
     .factory('TopoSelectService',
-        ['FnService', 'WebSocketService', 'TopoOverlayService',
+        ['$log', 'FnService', 'WebSocketService', 'TopoOverlayService',
         'TopoPanelService', 'TopoTrafficService', 'SvgUtilService',
 
-        function (_fs_, _wss_, _tov_, _tps_, _tts_, _sus_) {
+        function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _sus_) {
+            $log = _$log_;
             fs = _fs_;
             wss = _wss_;
             tov = _tov_;