ONOS-1479 -- GUI - augmenting topology view for extensibility:
- Preliminary work in implementing installation of custom buttons to details panel for selected device.

Change-Id: Id26ac301f72b4521d2a388d34ee0a287f400c68c
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 017925b..e6b4ac4 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -107,6 +107,7 @@
 import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
 import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
 import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
+import static org.onosproject.ui.topo.TopoConstants.*;
 
 /**
  * Facility for creating messages bound for the topology viewer.
@@ -448,15 +449,15 @@
         Topology topology = topologyService.currentTopology();
 
         return new PropertyPanel("ONOS Summary", "node")
-            .addProp("Devices", topology.deviceCount())
-            .addProp("Links", topology.linkCount())
-            .addProp("Hosts", hostService.getHostCount())
-            .addProp("Topology SCCs", topology.clusterCount())
+            .addProp(Properties.DEVICES, topology.deviceCount())
+            .addProp(Properties.LINKS, topology.linkCount())
+            .addProp(Properties.HOSTS, hostService.getHostCount())
+            .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
             .addSeparator()
-            .addProp("Intents", intentService.getIntentCount())
-            .addProp("Tunnels", tunnelService.tunnelCount())
-            .addProp("Flows", flowService.getFlowRuleCount())
-            .addProp("Version", version);
+            .addProp(Properties.INTENTS, intentService.getIntentCount())
+            .addProp(Properties.TUNNELS, tunnelService.tunnelCount())
+            .addProp(Properties.FLOWS, flowService.getFlowRuleCount())
+            .addProp(Properties.VERSION, version);
     }
 
     // Returns property panel model for device details response.
@@ -472,20 +473,20 @@
         String typeId = device.type().toString().toLowerCase();
 
         PropertyPanel pp = new PropertyPanel(title, typeId)
-                .id(deviceId.toString())
-                .addProp("URI", deviceId.toString())
-                .addProp("Vendor", device.manufacturer())
-                .addProp("H/W Version", device.hwVersion())
-                .addProp("S/W Version", device.swVersion())
-                .addProp("Serial Number", device.serialNumber())
-                .addProp("Protocol", annot.value(AnnotationKeys.PROTOCOL))
-                .addSeparator()
-                .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
-                .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE))
-                .addSeparator()
-                .addProp("Ports", portCount)
-                .addProp("Flows", flowCount)
-                .addProp("Tunnels", tunnelCount);
+            .id(deviceId.toString())
+            .addProp(Properties.URI, deviceId.toString())
+            .addProp(Properties.VENDOR, device.manufacturer())
+            .addProp(Properties.HW_VERSION, device.hwVersion())
+            .addProp(Properties.SW_VERSION, device.swVersion())
+            .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
+            .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
+            .addSeparator()
+            .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
+            .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
+            .addSeparator()
+            .addProp(Properties.PORTS, portCount)
+            .addProp(Properties.FLOWS, flowCount)
+            .addProp(Properties.TUNNELS, tunnelCount);
 
         // TODO: add button descriptors
 
@@ -570,13 +571,13 @@
         String typeId = isNullOrEmpty(type) ? "endstation" : type;
 
         PropertyPanel pp = new PropertyPanel(title, typeId)
-                .id(hostId.toString())
-                .addProp("MAC", host.mac())
-                .addProp("IP", host.ipAddresses(), "[\\[\\]]")
-                .addProp("VLAN", vlan.equals("-1") ? "none" : vlan)
-                .addSeparator()
-                .addProp("Latitude", annot.value(AnnotationKeys.LATITUDE))
-                .addProp("Longitude", annot.value(AnnotationKeys.LONGITUDE));
+            .id(hostId.toString())
+            .addProp(Properties.MAC, host.mac())
+            .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
+            .addProp(Properties.VLAN, vlan.equals("-1") ? "none" : vlan)
+            .addSeparator()
+            .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
+            .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
 
         // TODO: add button descriptors
         return pp;
@@ -859,6 +860,12 @@
         }
         result.set("propOrder", porder);
         result.set("props", pnode);
+
+        ArrayNode buttons = arrayNode();
+        for (PropertyPanel.Button b : pp.buttons()) {
+            buttons.add(b.id());
+        }
+        result.set("buttons", buttons);
         return result;
     }
 
diff --git a/web/gui/src/main/webapp/_sdh/overlaywork/AppUiTopoOverlay.java b/web/gui/src/main/webapp/_sdh/overlaywork/AppUiTopoOverlay.java
new file mode 100644
index 0000000..494885d
--- /dev/null
+++ b/web/gui/src/main/webapp/_sdh/overlaywork/AppUiTopoOverlay.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ *
+ */
+
+package org.meowster.over;
+
+import org.onosproject.ui.UiTopoOverlay;
+import org.onosproject.ui.topo.PropertyPanel;
+import org.onosproject.ui.topo.TopoConstants.Glyphs;
+
+import static org.onosproject.ui.topo.TopoConstants.Properties.*;
+
+/**
+ * Our topology overlay.
+ */
+public class AppUiTopoOverlay extends UiTopoOverlay {
+
+    // NOTE: this must match the ID defined in topov.js
+    private static final String OVERLAY_ID = "meowster-overlay";
+
+    private static final String MY_TITLE = "I changed the title";
+    private static final String MY_VERSION = "Beta-1.0.0042";
+    private static final String FOO = "foo";
+    private static final String BAR = "bar";
+
+
+    public AppUiTopoOverlay() {
+        super(OVERLAY_ID);
+    }
+
+
+    @Override
+    public void modifySummary(PropertyPanel pp) {
+        pp.title("My App Rocks!")
+                .typeId(Glyphs.CROWN)
+                .removeProps(
+                        TOPOLOGY_SSCS,
+                        INTENTS,
+                        TUNNELS,
+                        FLOWS,
+                        VERSION
+                )
+                .addProp(VERSION, MY_VERSION);
+
+    }
+
+    @Override
+    public void modifyDeviceDetails(PropertyPanel pp) {
+        pp.title(MY_TITLE);
+        pp.removeProps(LATITUDE, LONGITUDE);
+        pp.addButton(FOO).addButton(BAR);
+    }
+
+// TODO: override more methods, as required...
+
+}
diff --git a/web/gui/src/main/webapp/_sdh/overlaywork/README.txt b/web/gui/src/main/webapp/_sdh/overlaywork/README.txt
new file mode 100644
index 0000000..d7dcbec
--- /dev/null
+++ b/web/gui/src/main/webapp/_sdh/overlaywork/README.txt
@@ -0,0 +1,4 @@
+NOTE:
+
+This directory is putting under revision control some key files from
+an example stand-alone app, as I develop the topology overlay functionality.
diff --git a/web/gui/src/main/webapp/_sdh/overlaywork/topov.js b/web/gui/src/main/webapp/_sdh/overlaywork/topov.js
index b845d54..80a3fa9 100644
--- a/web/gui/src/main/webapp/_sdh/overlaywork/topov.js
+++ b/web/gui/src/main/webapp/_sdh/overlaywork/topov.js
@@ -7,21 +7,52 @@
 
     // our overlay definition
     var overlay = {
-        overlayId: 'sampleTopoOver',
+        // NOTE: this must match the ID defined in AppUiTopoOverlay
+        overlayId: 'meowster-overlay',
+        glyphId: '*star4',
+        tooltip: 'Sample Meowster Topo Overlay',
 
-        // NOTE: for the glyph, could alternately use id: <existingGlyphId>
-        //       instead of defining viewbox and data (vb, d)
-        //       glyph: { id: 'crown' }
-        glyph: {
-            vb: '0 0 8 8',
-            d: 'M1,4l2,-1l1,-2l1,2l2,1l-2,1l-1,2l-1,-2z'
+        // These glyphs get installed using the overlayId as a prefix.
+        // e.g. 'star4' is installed as 'meowster-overlay-star4'
+        // They can be referenced (from this overlay) as '*star4'
+        // That is, the '*' prefix stands in for 'meowster-overlay-'
+        glyphs: {
+            star4: {
+                vb: '0 0 8 8',
+                d: 'M1,4l2,-1l1,-2l1,2l2,1l-2,1l-1,2l-1,-2z'
+            },
+            banner: {
+                vb: '0 0 6 6',
+                d: 'M1,1v4l2,-2l2,2v-4z'
+            }
         },
-        tooltip: 'Sample Topology Overlay',
 
         activate: activateOverlay,
-        deactivate: deactivateOverlay
+        deactivate: deactivateOverlay,
+
+        // button descriptors - these can be added to overview or detail panels
+        buttons: {
+            foo: {
+                gid: 'chain',
+                tt: 'a FOO action',
+                cb: fooCb
+            },
+            bar: {
+                gid: '*banner',
+                tt: 'a BAR action',
+                cb: barCb
+            }
+        }
     };
 
+    function fooCb(data) {
+        $log.debug('FOO callback with data:', data);
+    }
+
+    function barCb(data) {
+        $log.debug('BAR callback with data:', data);
+    }
+
     // === implementation of overlay API (essentially callbacks)
     function activateOverlay() {
         $log.debug("sample topology overlay ACTIVATED");
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index e2b3125..9dddc9b 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -44,26 +44,36 @@
         $log.warn(tos + fn + '(): ' + msg);
     }
 
-    function handleGlyph(o) {
-        var gdata = fs.isO(o.glyph),
-            oid,
-            data = {};
+    function mkGlyphId(oid, gid) {
+        return (gid[0] === '*') ? oid + '-' + gid.slice(1) : gid;
+    }
 
-        if (!gdata) {
-            o._glyphId = 'unknown';
-        } else {
-            if (gdata.id) {
-                o._glyphId = gdata.id;
-            } else if (gdata.vb && gdata.d) {
-                oid = o.overlayId;
-                data['_' + oid] = gdata.vb;
-                data[oid] = gdata.d;
-                gs.registerGlyphs(data);
-                o._glyphId = oid;
-                $log.debug('registered overlay glyph:', oid);
-            } else {
-                warn('registerGlyph', 'problem with glyph data');
-            }
+    function handleGlyphs(o) {
+        var gdata = fs.isO(o.glyphs),
+            oid = o.overlayId,
+            gid = o.glyphId || 'unknown',
+            data = {},
+            note = [];
+
+        o._glyphId = mkGlyphId(oid, gid);
+
+        o.mkGid = function (g) {
+            return mkGlyphId(oid, g);
+        };
+        o.mkId = function (s) {
+            return oid + '-' + s;
+        };
+
+        // process glyphs if defined
+        if (gdata) {
+            angular.forEach(gdata, function (value, key) {
+                var fullkey = oid + '-' + key;
+                data['_' + fullkey] = value.vb;
+                data[fullkey] = value.d;
+                note.push('*' + key);
+            });
+            gs.registerGlyphs(data);
+            $log.debug('registered overlay glyphs:', oid, note);
         }
     }
 
@@ -79,7 +89,7 @@
             return warn(r, 'already registered: "' + id + '"');
         }
         overlays[id] = overlay;
-        handleGlyph(overlay);
+        handleGlyphs(overlay);
         $log.debug(tos + 'registered overlay: ' + id, overlay);
     }
 
@@ -132,6 +142,28 @@
         }
     }
 
+    // install buttons from the current overlay
+    function installButtons(bids, addFn, data) {
+        if (current) {
+            bids.forEach(function (bid) {
+                var btn = current.buttons[bid],
+                    funcWrap = function () {
+                        btn.cb(data);
+                    };
+
+                if (btn) {
+                    addFn({
+                        id: current.mkId(bid),
+                        gid: current.mkGid(btn.gid),
+                        cb: funcWrap,
+                        tt: btn.tt
+                    });
+                }
+            });
+        }
+
+    }
+
     angular.module('ovTopo')
     .factory('TopoOverlayService',
         ['$log', 'FnService', 'GlyphService', 'WebSocketService',
@@ -147,7 +179,8 @@
                 unregister: unregister,
                 list: list,
                 overlay: overlay,
-                tbSelection: tbSelection
+                tbSelection: tbSelection,
+                installButtons: installButtons
             }
         }]);
 
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 051ce05..c02ef51 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 $log, fs, wss, tps, tts, ns;
+    var $log, fs, wss, tov, tps, tts, ns;
 
     // api to topoForce
     var api;
@@ -229,9 +229,13 @@
     //  Event Handlers
 
     function showDetails(data) {
+        var buttons = fs.isA(data.buttons);
+
         // display the data for the single selected node
         tps.displaySingle(data);
 
+        // TODO: use server-side-button-descriptors to add buttons
+
         // always add the 'show traffic' action
         tps.addAction({
             id: '-sin-rel-traf-btn',
@@ -249,6 +253,13 @@
                 tt: 'Show Device Flows'
             });
         }
+
+        // TODO: for now, install overlay buttons here
+        if (buttons) {
+            tov.installButtons(buttons, tps.addAction, data);
+        }
+
+
         // TODO: have the server return explicit class and ID of each node
         // for now, we assume the node is a device if it has a URI
         if ((data.props).hasOwnProperty('URI')) {
@@ -308,13 +319,14 @@
 
     angular.module('ovTopo')
     .factory('TopoSelectService',
-        ['$log', 'FnService', 'WebSocketService',
+        ['$log', 'FnService', 'WebSocketService', 'TopoOverlayService',
             'TopoPanelService', 'TopoTrafficService', 'NavService',
 
-        function (_$log_, _fs_, _wss_, _tps_, _tts_, _ns_) {
+        function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _ns_) {
             $log = _$log_;
             fs = _fs_;
             wss = _wss_;
+            tov = _tov_;
             tps = _tps_;
             tts = _tts_;
             ns = _ns_;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
index 1da60c1..71cb94c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js
@@ -29,7 +29,7 @@
     // traffic overlay definition
     var overlay = {
         overlayId: 'traffic',
-        glyph: { id: 'allTraffic' },
+        glyphId: 'allTraffic',
         tooltip: 'Traffic Overlay',
 
         activate: activateTraffic,