CSS Added fill-mode for nodes
Amends based on Simons Comments
ESLinted The whole project
Topo2Link - Added Labels for PortA and PortB on mouseover
Updated breadcrumbs
Topo2.js - commented out a line causing error on panning
Topo2 Navigation between regions

Change-Id: I9cc0f4499ab68a14e246bba192f6528258471b35
diff --git a/web/gui/src/main/webapp/app/view/topo2/.eslintrc.js b/web/gui/src/main/webapp/app/view/topo2/.eslintrc.js
new file mode 100644
index 0000000..5934858
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/.eslintrc.js
@@ -0,0 +1,20 @@
+module.exports = {
+    "extends": "google",
+    "installedESLint": true,
+    "globals": {
+        "angular": true,
+        "d3": true
+    },
+    "rules": {
+        "brace-style": 0,
+        "no-void": 0,
+        "require-jsdoc": 0,
+        "padded-blocks": 0,
+        "quote-props": 0,
+        "no-warning-comments": 0,
+        "object-curly-spacing": ["error", "always"],
+        "indent": ["error", 4],
+        "one-var": 0,
+        "space-before-function-paren": ["error", { "anonymous": "always", "named": "never" }]
+    }
+};
diff --git a/web/gui/src/main/webapp/app/view/topo2/package.json b/web/gui/src/main/webapp/app/view/topo2/package.json
new file mode 100644
index 0000000..2adc677
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "topo2",
+  "version": "1.0.0",
+  "description": "ONOS Topo2",
+  "main": "topo2.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "lint": "./node_modules/.bin/eslint ./**/*.js"
+  },
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "eslint": "^3.4.0",
+    "eslint-config-google": "^0.6.0"
+  }
+}
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.css b/web/gui/src/main/webapp/app/view/topo2/topo2.css
index 160525c..c40dd1e 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.css
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.css
@@ -66,3 +66,10 @@
 .floatpanel {
     top: 104px;
 }
+
+
+/* -- Base Device Styles -- */
+#ov-topo2 svg .node {
+    cursor: pointer;
+    fill-rule: evenodd;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.js b/web/gui/src/main/webapp/app/view/topo2/topo2.js
index ab36e4e..e1dcb3e 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -24,18 +24,14 @@
     'use strict';
 
     // references to injected services
-    var $scope, $log, $loc,
-        fs, mast, ks, zs,
-        gs, ms, sus, flash,
-        wss, ps, th,
-        t2es, t2fs, t2is, t2bcs;
+    var $scope, $log, fs, mast, ks, zs,
+        gs, sus, ps, t2es, t2fs, t2is, t2bcs;
 
     // DOM elements
-    var ovtopo2, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer;
+    var ovtopo2, svg, defs, zoomLayer, forceG;
 
     // Internal state
-    var zoomer, actionMap;
-
+    var zoomer;
 
     // --- Glyphs, Icons, and the like -----------------------------------
 
@@ -66,10 +62,10 @@
         var sc = zoomer.scale(),
             tr = zoomer.translate();
 
-        ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc});
+        ps.setPrefs('topo_zoom', { tx: tr[0], ty: tr[1], sc: sc });
 
         // keep the map lines constant width while zooming
-        mapG.style('stroke-width', (2.0 / sc) + 'px');
+        // mapG.style('stroke-width', (2.0 / sc) + 'px');
     }
 
     function setUpZoom() {
@@ -82,7 +78,6 @@
         });
     }
 
-
     // === Controller Definition -----------------------------------------
 
     angular.module('ovTopo2', ['onosUtil', 'onosSvg', 'onosRemote'])
@@ -101,7 +96,6 @@
             _t2es_, _t2fs_, _t2is_, _t2bcs_) {
 
             var params = _$loc_.search(),
-                projection,
                 dim,
                 wh,
                 uplink = {
@@ -109,14 +103,13 @@
                     // showNoDevs: showNoDevs,
                     // projection: function () { return projection; },
                     zoomLayer: function () { return zoomLayer; },
-                    zoomer: function () { return zoomer; },
+                    zoomer: function () { return zoomer; }
                     // opacifyMap: opacifyMap,
                     // topoStartDone: topoStartDone
                 };
 
             $scope = _$scope_;
             $log = _$log_;
-            $loc = _$loc_;
 
             fs = _fs_;
             mast = _mast_;
@@ -124,13 +117,9 @@
             zs = _zs_;
 
             gs = _gs_;
-            ms = _ms_;
             sus = _sus_;
-            flash = _flash_;
 
-            wss = _wss_;
             ps = _ps_;
-            th = _th_;
 
             t2es = _t2es_;
             t2fs = _t2fs_;
@@ -140,7 +129,10 @@
             // capture selected intent parameters (if they are set in the
             //  query string) so that the traffic overlay can highlight
             //  the path for that intent
-            if (params.intentKey && params.intentAppId && params.intentAppName) {
+            if (params.intentKey &&
+                params.intentAppId &&
+                params.intentAppName) {
+
                 $scope.intentData = {
                     key: params.intentKey,
                     appId: params.intentAppId,
@@ -169,7 +161,6 @@
             svg.attr(wh);
             dim = [wh.width, wh.height];
 
-
             // set up our keyboard shortcut bindings
             setUpKeys();
             setUpZoom();
@@ -183,7 +174,6 @@
             t2fs.init(svg, forceG, uplink, dim);
             t2bcs.init();
 
-
             // =-=-=-=-=-=-=-=-
             // TODO: in future, we will load background map data
             //  asynchronously (hence the promise) and then chain off
@@ -191,12 +181,8 @@
             // For now, we'll send the event inline...
             t2es.start();
 
-
-
             t2is.initInst({ showMastership: t2fs.showMastership });
 
-
-
             // === ORIGINAL CODE ===
 
             // setUpKeys();
@@ -242,4 +228,4 @@
 
             $log.log('OvTopo2Ctrl has been created');
         }]);
-}());
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js b/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js
index d089acb..c56ddf68 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Breadcrumb.js
@@ -19,16 +19,18 @@
  Module that renders the breadcrumbs for regions
  */
 
- (function () {
+(function () {
+
     'use strict';
 
     var $log, wss;
 
+    // Internal
     var breadcrumbContainer,
         breadcrumbs;
 
     function init() {
-
+        $log.debug("Topo2BreadcrumbService Initiated");
         breadcrumbs = [];
         breadcrumbContainer = d3.select('#breadcrumbs');
         render();
@@ -36,13 +38,8 @@
 
     function addBreadcrumb(crumbs) {
 
-        // If `crumbs` is an array, merge with breadcrumbs;
-        if (crumbs.length) {
-            breadcrumbs = breadcrumbs.concat(crumbs);
-        } else {
-            breadcrumbs.push(crumbs);
-        }
-
+        breadcrumbContainer.selectAll('.breadcrumb').remove();
+        breadcrumbs = crumbs.reverse();
         render();
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
index e0aefb7..36f4264 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
@@ -26,12 +26,14 @@
 
     function Collection(models, options) {
 
-        options || (options = {});
+        var opts = options || (options = {});
 
         this.models = [];
         this._reset();
 
-        if (options.comparator !== void 0) this.comparator = options.comparator;
+        if (opts.comparator) {
+            this.comparator = opts.comparator;
+        }
 
         if (models) {
             this.add(models);
@@ -48,7 +50,8 @@
 
                 data.forEach(function (d) {
 
-                    var model = new _this.model(d);
+                    var CollectionModel = _this.model;
+                    var model = new CollectionModel(d);
                     model.collection = _this;
 
                     _this.models.push(model);
@@ -57,9 +60,11 @@
             }
         },
         get: function (id) {
+
             if (!id) {
-                return void 0;
+                return null;
             }
+
             return this._byId[id] || null;
         },
         sort: function () {
@@ -76,9 +81,11 @@
             this._byId = [];
             this.models = [];
         },
-        toJSON: function(options) {
-            return this.models.map(function(model) { return model.toJSON(options); });
-        },
+        toJSON: function (options) {
+            return this.models.map(function (model) {
+                return model.toJSON(options);
+            });
+        }
     };
 
     Collection.extend = function (protoProps, staticProps) {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2D3.js b/web/gui/src/main/webapp/app/view/topo2/topo2D3.js
index 2cd4ba7..d32b55d 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2D3.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2D3.js
@@ -22,99 +22,21 @@
 (function () {
     'use strict';
 
-    var sus, is, ts;
+    var is;
 
-    // internal state
-    var deviceLabelIndex = 0,
-    hostLabelIndex = 0;
-
-    // configuration
-    var devIconDim = 36,
-        labelPad = 4,
-        hostRadius = 14,
-        badgeConfig = {
-            radius: 12,
-            yoff: 5,
-            gdelta: 10
-        },
-        halfDevIcon = devIconDim / 2,
-        devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
-        hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
-        status = {
-            i: 'badgeInfo',
-            w: 'badgeWarn',
-            e: 'badgeError'
-        };
-
-    // note: these are the device icon colors without affinity (no master)
-    var dColTheme = {
-        light: {
-            online: '#444444',
-            offline: '#cccccc'
-        },
-        dark: {
-            // TODO: theme
-            online: '#444444',
-            offline: '#cccccc'
-        }
-    };
+    // Configuration
+    var hostRadius = 14;
 
     function init() {}
 
-    function renderBadge(node, bdg, boff) {
-        var bsel,
-            bcr = badgeConfig.radius,
-            bcgd = badgeConfig.gdelta;
-
-        node.select('g.badge').remove();
-
-        bsel = node.append('g')
-            .classed('badge', true)
-            .classed(badgeStatus(bdg), true)
-            .attr('transform', sus.translate(boff.dx, boff.dy));
-
-        bsel.append('circle')
-            .attr('r', bcr);
-
-        if (bdg.txt) {
-            bsel.append('text')
-                .attr('dy', badgeConfig.yoff)
-                .attr('text-anchor', 'middle')
-                .text(bdg.txt);
-        } else if (bdg.gid) {
-            bsel.append('use')
-                .attr({
-                    width: bcgd * 2,
-                    height: bcgd * 2,
-                    transform: sus.translate(-bcgd, -bcgd),
-                    'xlink:href': '#' + bdg.gid
-                });
-        }
-    }
-
-    // TODO: Move to Device Model when working on the Exit Devices
-    function updateDeviceRendering(d) {
-        var node = d.el,
-            bdg = d.badge,
-            label = trimLabel(deviceLabel(d)),
-            labelWidth;
-
-        node.select('text').text(label);
-        labelWidth = label ? computeLabelWidth(node) : 0;
-
-        node.select('rect')
-            .transition()
-            .attr(iconBox(devIconDim, labelWidth));
-
-        if (bdg) {
-            renderBadge(node, bdg, devBadgeOff);
-        }
-    }
-
     function nodeEnter(node) {
         node.onEnter(this, node);
     }
 
+    function nodeExit(node) {
+        node.onExit(this, node);
+    }
+
     function hostLabel(d) {
         return d.get('id');
 
@@ -144,19 +66,18 @@
 
     angular.module('ovTopo2')
     .factory('Topo2D3Service',
-    ['SvgUtilService', 'IconService', 'ThemeService',
+    ['IconService',
 
-        function (_sus_, _is_, _ts_) {
-            sus = _sus_;
+        function (_is_) {
             is = _is_;
-            ts = _ts_;
 
             return {
                 init: init,
                 nodeEnter: nodeEnter,
+                nodeExit: nodeExit,
                 hostEnter: hostEnter,
                 linkEntering: linkEntering
-            }
+            };
         }
     ]
 );
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
index 6e48b83..9c4ccc2 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
@@ -22,7 +22,7 @@
 (function () {
     'use strict';
 
-    var Collection, Model, is, sus, ts, t2vs;
+    var Collection, Model, is, sus, ts;
 
     var remappedDeviceTypes = {
         virtual: 'cord'
@@ -30,30 +30,17 @@
 
     // configuration
     var devIconDim = 36,
-        labelPad = 10,
-        hostRadius = 14,
-        badgeConfig = {
-            radius: 12,
-            yoff: 5,
-            gdelta: 10
-        },
-        halfDevIcon = devIconDim / 2,
-        devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
-        hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
-        status = {
-            i: 'badgeInfo',
-            w: 'badgeWarn',
-            e: 'badgeError'
-        },
-        deviceLabelIndex = 0;
+        halfDevIcon = devIconDim / 2;
 
     function createDeviceCollection(data, region) {
 
         var DeviceCollection = Collection.extend({
             model: Model,
-            comparator: function(a, b) {
-                var order = region.get('layerOrder');
-                return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer'));
+            comparator: function (a, b) {
+                var order = region.get('layerOrder'),
+                    aLayer = a.get('layer'),
+                    bLayer = b.get('layer');
+                return order.indexOf(aLayer - order.indexOf(bLayer));
             }
         });
 
@@ -80,7 +67,7 @@
             y: -dim / 2,
             width: dim + labelWidth,
             height: dim
-        }
+        };
     }
 
     // note: these are the device icon colors without affinity (no master)
@@ -100,8 +87,9 @@
         var o = this.node.online,
             id = this.node.master, // TODO: This should be from node.master
             otag = o ? 'online' : 'offline';
-        return o ? sus.cat7().getColor(id, 0, ts.theme())
-                 : dColTheme[ts.theme()][otag];
+
+        return o ? sus.cat7().getColor(id, 0, ts.theme()) :
+            dColTheme[ts.theme()][otag];
     }
 
     function setDeviceColor() {
@@ -111,18 +99,17 @@
 
     angular.module('ovTopo2')
     .factory('Topo2DeviceService',
-        ['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
-        'ThemeService', 'Topo2ViewService',
+        ['Topo2Collection', 'Topo2NodeModel', 'IconService',
+        'SvgUtilService', 'ThemeService',
 
-            function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
+            function (_c_, _nm_, _is_, _sus_, _ts_, classnames) {
 
-                t2vs = _t2vs_;
                 is = _is_;
                 sus = _sus_;
                 ts = _ts_;
-                Collection = _Collection_;
+                Collection = _c_;
 
-                Model = _NodeModel_.extend({
+                Model = _nm_.extend({
                     initialize: function () {
                         this.set('weight', 0);
                         this.constructor.__super__.initialize.apply(this, arguments);
@@ -152,7 +139,19 @@
                         node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
                         this.render();
                     },
-                    onExit: function () {},
+                    onExit: function () {
+                        var node = this.el;
+                        node.select('use')
+                            .style('opacity', 0.5)
+                            .transition()
+                            .duration(800)
+                            .style('opacity', 0);
+
+                        node.selectAll('rect')
+                            .style('stroke-fill', '#555')
+                            .style('fill', '#888')
+                            .style('opacity', 0.5);
+                    },
                     render: function () {
                         this.setDeviceColor();
                     }
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
index 2d13a5c..705173c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
@@ -34,7 +34,6 @@
         openListener;
 
     // TODO: only add heartbeat timer etc. if we really need to be doing that..
-
     // ========================== Helper Functions
 
     function createHandlerMap() {
@@ -100,4 +99,4 @@
                 stop: stop
             };
         }]);
-}());
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
index fb9d09e4..8eb89d1 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
@@ -26,43 +26,12 @@
     var $log,
         wss;
 
-    // SVG elements;
-    var linkG,
-        linkLabelG,
-        numLinkLblsG,
-        portLabelG,
-        nodeG;
-
-    // internal state
-    var settings,   // merged default settings and options
-        force,      // force layout object
-        drag,       // drag behavior handler
-        network = {
-            nodes: [],
-            links: [],
-            linksByDevice: {},
-            lookup: {},
-            revLinkToKey: {}
-        },
-        lu,                     // shorthand for lookup
-        rlk,                    // shorthand for revLinktoKey
-        showHosts = false,      // whether hosts are displayed
-        showOffline = true,     // whether offline devices are displayed
-        nodeLock = false,       // whether nodes can be dragged or not (locked)
-        fTimer,                 // timer for delayed force layout
-        fNodesTimer,            // timer for delayed nodes update
-        fLinksTimer,            // timer for delayed links update
-        dim,                    // the dimensions of the force layout [w,h]
-        linkNums = [];          // array of link number labels
-
-    // D3 selections;
-    var link,
-        linkLabel,
-        node;
-
-    var $log, wss, t2is, t2rs, t2ls, t2vs, t2bcs;
+    var t2is, t2rs, t2ls, t2vs, t2bcs;
     var svg, forceG, uplink, dim, opts;
 
+    // D3 Selections
+    var node;
+
     // ========================== Helper Functions
 
     function init(_svg_, _forceG_, _uplink_, _dim_, _opts_) {
@@ -70,7 +39,9 @@
         forceG = _forceG_;
         uplink = _uplink_;
         dim = _dim_;
-        opts = _opts_
+        opts = _opts_;
+
+        t2ls.init(svg, forceG, uplink, dim, opts);
     }
 
     function destroy() {
@@ -91,7 +62,7 @@
         var parentRegion = data.parent;
         var span = topdiv.select('.parentRegion').select('span');
         span.text(parentRegion || '[no parent]');
-        span.classed('nav-me', !!parentRegion);
+        span.classed('nav-me', Boolean(parentRegion));
     }
 
     function doTmpCurrentRegion(data) {
@@ -162,30 +133,23 @@
         $log.debug('>> topo2CurrentRegion event:', data);
         doTmpCurrentRegion(data);
         t2rs.addRegion(data);
-        t2ls.init(svg, forceG, uplink, dim, opts);
-        t2ls.update();
-        t2ls.start();
+        t2ls.createForceLayout();
     }
 
     function topo2PeerRegions(data) {
-        $log.debug('>> topo2PeerRegions event:', data)
+        $log.debug('>> topo2PeerRegions event:', data);
         doTmpPeerRegions(data);
     }
 
-    function topo2PeerRegions(data) {
-        $log.debug('>> topo2PeerRegions event:', data)
-    }
-
     function startDone(data) {
         $log.debug('>> topo2StartDone event:', data);
     }
 
-
     function showMastership(masterId) {
-        if (!masterId) {
-            restoreLayerState();
-        } else {
+        if (masterId) {
             showMastershipFor(masterId);
+        } else {
+            restoreLayerState();
         }
     }
 
@@ -224,10 +188,6 @@
         t2ls.setDimensions();
     }
 
-    function getDim() {
-        return dim;
-    }
-
     // ========================== Main Service Definition
 
     angular.module('ovTopo2')
@@ -259,4 +219,4 @@
                 topo2PeerRegions: topo2PeerRegions
             };
         }]);
-}());
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
index 25d088a..d67fcff 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
@@ -22,7 +22,7 @@
 (function () {
     'use strict';
 
-    var Collection, Model, t2vs;
+    var Collection, Model;
 
     function createHostCollection(data, region) {
 
@@ -41,12 +41,10 @@
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2HostService',
-    [
+    .factory('Topo2HostService', [
         'Topo2Collection', 'Topo2NodeModel', 'Topo2ViewService',
         function (_Collection_, _NodeModel_, classnames, _t2vs_) {
 
-            t2vs = _t2vs_;
             Collection = _Collection_;
 
             Model = _NodeModel_.extend({
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js b/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
index 31c8886..b35f10c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Instance.js
@@ -2,13 +2,7 @@
     'use strict';
 
     // injected refs
-    var $log,
-        ps,
-        sus,
-        gs,
-        ts,
-        fs,
-        flash;
+    var $log, ps, sus, gs, ts;
 
     // api from topo
     var api;
@@ -24,10 +18,8 @@
     // internal state
     var onosInstances,
         onosOrder,
-        oiShowMaster,
         oiBox;
 
-
     function addInstance(data) {
         var id = data.id;
 
@@ -51,30 +43,15 @@
         }
     }
 
-    function removeInstance(data) {
-        var id = data.id,
-            d = onosInstances[id];
-        if (d) {
-            var idx = fs.find(id, onosOrder);
-            if (idx >= 0) {
-                onosOrder.splice(idx, 1);
-            }
-            delete onosInstances[id];
-            updateInstances();
-        } else {
-            logicError('removeInstance lookup fail. ID = "' + id + '"');
-        }
-    }
-
     // ==========================
 
     function clickInst(d) {
         var el = d3.select(this),
             aff = el.classed('affinity');
-        if (!aff) {
-            setAffinity(el, d);
-        } else {
+        if (aff) {
             cancelAffinity();
+        } else {
+            setAffinity(el, d);
         }
     }
 
@@ -86,7 +63,6 @@
 
         // suppress all elements except nodes whose master is this instance
         api.showMastership(d.id);
-        oiShowMaster = true;
     }
 
     function cancelAffinity() {
@@ -94,7 +70,6 @@
             .classed('mastership affinity', false);
 
         api.showMastership(null);
-        oiShowMaster = false;
     }
 
     function attachUiBadge(svg) {
@@ -173,7 +148,6 @@
             updAttr('ns', nSw(d.switches));
         });
 
-
         // operate on new onos instances
         var entering = onoses.enter()
             .append('div')
@@ -235,9 +209,7 @@
         onoses.exit().remove();
     }
 
-
     // ==========================
-
     function logicError(msg) {
         if (showLogicErrors) {
             $log.warn('TopoInstService: ' + msg);
@@ -251,23 +223,11 @@
 
         onosInstances = {};
         onosOrder = [];
-        oiShowMaster = false;
 
         // we want to update the instances, each time the theme changes
         ts.addListener(updateInstances);
     }
 
-    function destroyInst() {
-        ts.removeListener(updateInstances);
-
-        ps.destroyPanel(idIns);
-        oiBox = null;
-
-        onosInstances = {};
-        onosOrder = [];
-        oiShowMaster = false;
-    }
-
     function allInstances(data) {
         $log.debug('Update all instances', data);
 
@@ -281,16 +241,14 @@
     angular.module('ovTopo2')
         .factory('Topo2InstanceService',
         ['$log', 'PanelService', 'SvgUtilService', 'GlyphService',
-        'ThemeService', 'FnService', 'FlashService',
+        'ThemeService',
 
-        function (_$log_, _ps_, _sus_, _gs_, _ts_, _fs_, _flash_) {
+        function (_$log_, _ps_, _sus_, _gs_, _ts_) {
             $log = _$log_;
             ps = _ps_;
             sus = _sus_;
             gs = _gs_;
             ts = _ts_;
-            fs = _fs_;
-            flash = _flash_;
 
             return {
                 initInst: initInst,
@@ -298,4 +256,4 @@
             };
         }]);
 
-}());
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index 9b4dc5e..c23bc7e 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -24,12 +24,10 @@
 
     var $log, sus, t2rs, t2d3, t2vs, t2ss;
 
-    var uplink, linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
-    var link, linkLabel, node;
+    var uplink, linkG, linkLabelG, nodeG;
+    var link, node;
 
-    var nodes, links, highlightedLink;
-
-    var force;
+    var highlightedLink;
 
     // default settings for force layout
     var defaultSettings = {
@@ -51,9 +49,9 @@
         linkStrength: {
             // note: key is link.type
             // range: {0.0 ... 1.0}
-            //direct: 1.0,
-            //optical: 1.0,
-            //hostLink: 1.0,
+            direct: 1.0,
+            optical: 1.0,
+            hostLink: 1.0,
             _def_: 1.0
         }
     };
@@ -79,23 +77,7 @@
     var settings,   // merged default settings and options
         force,      // force layout object
         drag,       // drag behavior handler
-        network = {
-            nodes: [],
-            links: [],
-            linksByDevice: {},
-            lookup: {},
-            revLinkToKey: {}
-        },
-        lu,                     // shorthand for lookup
-        rlk,                    // shorthand for revLinktoKey
-        showHosts = false,      // whether hosts are displayed
-        showOffline = true,     // whether offline devices are displayed
-        nodeLock = false,       // whether nodes can be dragged or not (locked)
-        fTimer,                 // timer for delayed force layout
-        fNodesTimer,            // timer for delayed nodes update
-        fLinksTimer,            // timer for delayed links update
-        dim,                    // the dimensions of the force layout [w,h]
-        linkNums = [];          // array of link number labels
+        nodeLock = false;       // whether nodes can be dragged or not (locked)
 
     var tickStuff = {
         nodeAttr: {
@@ -110,14 +92,6 @@
             y1: function (d) { return d.get('position').y1; },
             x2: function (d) { return d.get('position').x2; },
             y2: function (d) { return d.get('position').y2; }
-        },
-        linkLabelAttr: {
-            transform: function (d) {
-                var lnk = tms.findLinkById(d.get('key'));
-                if (lnk) {
-                    return t2d3.transformLabel(lnk.get('position'));
-                }
-            }
         }
     };
 
@@ -129,14 +103,19 @@
 
         linkG = forceG.append('g').attr('id', 'topo-links');
         linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
-        numLinkLabelsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
+        forceG.append('g').attr('id', 'topo-numLinkLabels');
         nodeG = forceG.append('g').attr('id', 'topo-nodes');
-        portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
+        forceG.append('g').attr('id', 'topo-portLabels');
 
         link = linkG.selectAll('.link');
-        linkLabel = linkLabelG.selectAll('.linkLabel');
+        linkLabelG.selectAll('.linkLabel');
         node = nodeG.selectAll('.node');
 
+        _svg_.on('mousemove', mouseMoveHandler);
+    }
+
+    function createForceLayout() {
+
         force = d3.layout.force()
             .size(t2vs.getDimensions())
             .nodes(t2rs.regionNodes())
@@ -148,10 +127,11 @@
             .linkStrength(settings.linkStrength._def_)
             .on('tick', tick);
 
-            drag = sus.createDragBehavior(force,
-                t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
+        drag = sus.createDragBehavior(force,
+            t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
 
-            _svg_.on('mousemove', mouseMoveHandler)
+        start();
+        update();
     }
 
     function zoomingOrPanning(ev) {
@@ -188,9 +168,6 @@
                 .attr(tickStuff.linkAttr);
             // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
         }
-        if (linkLabel && linkLabel.size()) {
-            linkLabel.attr(tickStuff.linkLabelAttr);
-        }
     }
 
     function update() {
@@ -210,7 +187,7 @@
             .append('g')
             .attr({
                 id: function (d) { return sus.safeId(d.get('id')); },
-                class: function (d) { return d.svgClassName() },
+                class: function (d) { return d.svgClassName(); },
                 transform: function (d) {
                     // Need to guard against NaN here ??
                     return sus.translate(d.node.x, d.node.y);
@@ -227,10 +204,18 @@
         entering.filter('.sub-region').each(t2d3.nodeEnter);
         entering.filter('.host').each(t2d3.hostEnter);
 
-        // operate on both existing and new nodes:
-        // node.filter('.device').each(function (device) {
-        //     t2d3.updateDeviceColors(device);
-        // });
+        // operate on exiting nodes:
+        // Note that the node is removed after 2 seconds.
+        // Sub element animations should be shorter than 2 seconds.
+        var exiting = node.exit()
+            .transition()
+            .duration(2000)
+            .style('opacity', 0)
+            .remove();
+
+        // exiting node specifics:
+        // exiting.filter('.host').each(t2d3.hostExit);
+        exiting.filter('.device').each(t2d3.nodeExit);
     }
 
     function _updateLinks() {
@@ -241,16 +226,6 @@
         link = linkG.selectAll('.link')
             .data(regionLinks, function (d) { return d.get('key'); });
 
-        // operate on existing links:
-        link.each(function (d) {
-            // this is supposed to be an existing link, but we have observed
-            //  occasions (where links are deleted and added rapidly?) where
-            //  the DOM element has not been defined. So protect against that...
-            if (d.el) {
-                restyleLinkElement(d, true);
-            }
-        });
-
         // operate on entering links:
         var entering = link.enter()
             .append('line')
@@ -260,14 +235,14 @@
                 y1: function (d) { return d.get('position').y1; },
                 x2: function (d) { return d.get('position').x2; },
                 y2: function (d) { return d.get('position').y2; },
-                stroke: linkConfig['light'].inColor,
+                stroke: linkConfig.light.inColor,
                 'stroke-width': linkConfig.inWidth
             });
 
         entering.each(t2d3.linkEntering);
 
         // operate on both existing and new links:
-        //link.each(...)
+        // link.each(...)
 
         // add labels for how many links are in a thick line
         // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
@@ -278,7 +253,7 @@
         // operate on exiting links:
         link.exit()
             .attr('stroke-dasharray', '3 3')
-            .attr('stroke', linkConfig['light'].outColor)
+            .attr('stroke', linkConfig.light.outColor)
             .style('opacity', 0.5)
             .transition()
             .duration(1500)
@@ -291,33 +266,20 @@
     }
 
     function calcPosition() {
-        var lines = this,
-            linkSrcId,
-            linkNums = [];
+        var lines = this;
 
-		lines.each(function (d) {
+        lines.each(function (d) {
             if (d.get('type') === 'hostLink') {
                 d.set('position', getDefaultPos(d));
             }
         });
 
-        function normalizeLinkSrc(link) {
-            // ensure source device is consistent across set of links
-            // temporary measure until link modeling is refactored
-            if (!linkSrcId) {
-                linkSrcId = link.source.id;
-                return false;
-            }
-
-            return link.source.id !== linkSrcId;
-        }
-
         lines.each(function (d) {
             d.set('position', getDefaultPos(d));
         });
     }
 
-	function getDefaultPos(link) {
+    function getDefaultPos(link) {
 
         return {
             x1: link.get('source').x,
@@ -333,7 +295,6 @@
         }
     }
 
-
     function start() {
         force.start();
     }
@@ -343,7 +304,6 @@
         var mp = getLogicalMousePosition(this),
             link = computeNearestLink(mp);
 
-
         if (highlightedLink) {
             highlightedLink.unenhance();
             highlightedLink = null;
@@ -363,11 +323,12 @@
             tr = uplink.zoomer().translate(),
             mx = (m[0] - tr[0]) / sc,
             my = (m[1] - tr[1]) / sc;
-        return {x: mx, y: my};
+        return { x: mx, y: my };
     }
 
-
-    function sq(x) { return x * x; }
+    function sq(x) {
+        return x * x;
+    }
 
     function mdist(p, m) {
         return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
@@ -377,33 +338,6 @@
         return dist / uplink.zoomer().scale();
     }
 
-    function computeNearestNode(mouse) {
-        var proximity = prox(30),
-            nearest = null,
-            minDist,
-            regionNodes = t2rs.regionNodes();
-
-        if (regionNodes.length) {
-            minDist = proximity * 2;
-
-            regionNodes.forEach(function (d) {
-                var dist;
-
-                if (!api.showHosts() && d.class === 'host') {
-                    return; // skip hidden hosts
-                }
-
-                dist = mdist({x: d.x, y: d.y}, mouse);
-                if (dist < minDist && dist < proximity) {
-                    minDist = dist;
-                    nearest = d;
-                }
-            });
-        }
-        return nearest;
-    }
-
-
     function computeNearestLink(mouse) {
         var proximity = prox(30),
             nearest = null,
@@ -418,11 +352,11 @@
                 y2 = line.y2,
                 x3 = mouse.x,
                 y3 = mouse.y,
-                k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) /
-                    (sq(y2-y1) + sq(x2-x1)),
-                x4 = x3 - k * (y2-y1),
-                y4 = y3 + k * (x2-x1);
-            return {x:x4, y:y4};
+                k = ((y2 - y1) * (x3 - x1) - (x2 - x1) * (y3 - y1)) /
+                    (sq(y2 - y1) + sq(x2 - x1)),
+                x4 = x3 - k * (y2 - y1),
+                y4 = y3 + k * (x2 - x1);
+            return { x: x4, y: y4 };
         }
 
         function lineHit(line, p, m) {
@@ -476,11 +410,12 @@
 
                 return {
                     init: init,
+                    createForceLayout: createForceLayout,
                     update: update,
                     start: start,
 
                     setDimensions: setDimensions
-                }
+                };
             }
         ]
     );
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
index dcf1b28..74fe8a2 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -23,7 +23,7 @@
     'use strict';
 
     var $log;
-    var Collection, Model, region, ts, sus;
+    var Collection, Model, ts, sus;
 
     var linkLabelOffset = '0.35em';
 
@@ -52,9 +52,6 @@
         outWidth: 10
     };
 
-    var defaultLinkType = 'direct',
-        nearDist = 15;
-
     function createLink() {
 
         var linkPoints = this.linkEndPoints(this.get('epA'), this.get('epB'));
@@ -92,14 +89,18 @@
         return box;
     }
 
+    function isLinkOnline(node) {
+        return (node.get('nodeType') === 'region') ? true : node.get('online');
+    }
+
     function linkEndPoints(srcId, dstId) {
 
-        var sourceNode = this.region.findNodeById(srcId)
-        var targetNode = this.region.findNodeById(dstId)
+        var sourceNode = this.region.findNodeById(srcId);
+        var targetNode = this.region.findNodeById(dstId);
 
         if (!sourceNode || !targetNode) {
             $log.error('Node(s) not on map for link:' + srcId + ':' + dstId);
-            //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
+            // logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
             return null;
         }
 
@@ -126,10 +127,13 @@
                 return true;
             },
             online: function () {
-                // TODO: remove next line
-                return true;
 
-                return both && (s && s.online) && (t && t.online);
+                var source = this.get('source'),
+                    target = this.get('target'),
+                    sourceOnline = isLinkOnline(source),
+                    targetOnline = isLinkOnline(target);
+
+                return (sourceOnline) && (targetOnline);
             },
             enhance: function () {
                 var data = [],
@@ -147,7 +151,17 @@
                 });
                 data.push(point);
 
-                var entering = d3.select('#topo-portLabels').selectAll('.portLabel')
+                if (this.get('portA')) {
+                    point = this.locatePortLabel(1);
+                    angular.extend(point, {
+                        id: 'topo-port-src',
+                        num: this.get('portA')
+                    });
+                    data.push(point);
+                }
+
+                var entering = d3.select('#topo-portLabels')
+                    .selectAll('.portLabel')
                     .data(data).enter().append('g')
                     .classed('portLabel', true)
                     .attr('id', function (d) { return d.id; });
@@ -171,7 +185,7 @@
                 this.el.classed('enhanced', false);
                 d3.select('#topo-portLabels').selectAll('.portLabel').remove();
             },
-            locatePortLabel: function (link, src) {
+            locatePortLabel: function (src) {
                 var offset = 32,
                     pos = this.get('position'),
                     nearX = src ? pos.x1 : pos.x2,
@@ -179,13 +193,15 @@
                     farX = src ? pos.x2 : pos.x1,
                     farY = src ? pos.y2 : pos.y1;
 
-                function dist(x, y) { return Math.sqrt(x*x + y*y); }
+                function dist(x, y) {
+                    return Math.sqrt(x * x + y * y);
+                }
 
                 var dx = farX - nearX,
                     dy = farY - nearY,
                     k = offset / dist(dx, dy);
 
-                return {x: k * dx + nearX, y: k * dy + nearY};
+                return { x: k * dx + nearX, y: k * dy + nearY };
             },
             restyleLinkElement: function (immediate) {
                 // this fn's job is to look at raw links and decide what svg classes
@@ -219,21 +235,19 @@
                 }
             },
             onEnter: function (el) {
-                var _this = this,
-                    link = d3.select(el);
+                var link = d3.select(el);
 
                 this.el = link;
-
                 this.restyleLinkElement();
 
                 if (this.get('type') === 'hostLink') {
-                    sus.visible(link, api.showHosts());
+                    // sus.visible(link, api.showHosts());
                 }
             }
         });
 
         var LinkCollection = Collection.extend({
-            model: LinkModel,
+            model: LinkModel
         });
 
         return new LinkCollection(data);
@@ -241,7 +255,8 @@
 
     angular.module('ovTopo2')
     .factory('Topo2LinkService',
-        ['$log', 'Topo2Collection', 'Topo2Model', 'ThemeService', 'SvgUtilService',
+        ['$log', 'Topo2Collection', 'Topo2Model',
+        'ThemeService', 'SvgUtilService',
 
             function (_$log_, _Collection_, _Model_, _ts_, _sus_) {
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
index 20fb5e0..b1b2a12 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
@@ -42,7 +42,7 @@
             return this.attributes[attr];
         },
 
-        set: function(key, val, options) {
+        set: function (key, val, options) {
 
             if (!key) {
                 return this;
@@ -56,19 +56,21 @@
                 (attributes = {})[key] = val;
             }
 
-            options || (options = {});
+            var opts = options || (options = {});
 
-            var unset = options.unset,
-                silent = options.silent,
+            var unset = opts.unset,
+                silent = opts.silent,
                 changes = [],
-                changing   = this._changing;
+                changing = this._changing;
 
             this._changing = true;
 
             if (!changing) {
 
                 // NOTE: angular.copy causes issues in chrome
-                this._previousAttributes = Object.create(Object.getPrototypeOf(this.attributes));
+                this._previousAttributes = Object.create(
+                    Object.getPrototypeOf(this.attributes)
+                );
                 this.changed = {};
             }
 
@@ -84,34 +86,38 @@
                     changes.push(index);
                 }
 
-                if (!angular.equals(previous[index], val)) {
-                    changed[index] = val;
-                } else {
+                if (angular.equals(previous[index], val)) {
                     delete changed[index];
+                } else {
+                    changed[index] = val;
                 }
 
-                unset ? delete current[index] : current[index] = val;
+                if (unset) {
+                    delete current[index];
+                } else {
+                    current[index] = val;
+                }
             });
 
             // Trigger all relevant attribute changes.
             if (!silent) {
                 if (changes.length) {
-                    this._pending = options;
+                    this._pending = opts;
                 }
                 for (var i = 0; i < changes.length; i++) {
-                    this.onChange(changes[i], this, current[changes[i]], options);
+                    this.onChange(changes[i], this,
+                        current[changes[i]], opts);
                 }
             }
 
             this._changing = false;
             return this;
         },
-        toJSON: function(options) {
-            return angular.copy(this.attributes)
-        },
+        toJSON: function (options) {
+            return angular.copy(this.attributes);
+        }
     };
 
-
     Model.extend = function (protoProps, staticProps) {
 
         var parent = this;
@@ -136,8 +142,7 @@
     };
 
     angular.module('ovTopo2')
-    .factory('Topo2Model',
-    [
+    .factory('Topo2Model', [
         function () {
             return Model;
         }
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
index 0e617dd..05dc4b9 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
@@ -25,9 +25,8 @@
     var randomService;
     var fn;
 
-    //internal state;
-    var defaultLinkType = 'direct',
-        nearDist = 15;
+    // Internal state;
+    var nearDist = 15;
 
     var devIconDim = 36,
         labelPad = 10,
@@ -42,9 +41,9 @@
             dim = [800, 600],
             xy;
 
-        // if the device contains explicit LONG/LAT data, use that to position
+        // If the device contains explicit LONG/LAT data, use that to position
         if (setLongLat(node)) {
-            //indicate we want to update cached meta data...
+            // Indicate we want to update cached meta data...
             return true;
         }
 
@@ -108,7 +107,7 @@
 
     angular.module('ovTopo2')
     .factory('Topo2NodeModel',
-        ['Topo2Model', 'FnService',  'RandomService',
+        ['Topo2Model', 'FnService', 'RandomService',
         function (Model, _fn_, _RandomService_) {
 
             randomService = _RandomService_;
@@ -118,25 +117,28 @@
                 initialize: function () {
                     this.node = this.createNode();
                 },
+                onEnter: function () {}, // To be overridden by sub-class
+                onExit: function () {}, // To be overridden by sub-class
                 label: function () {
 
                     var props = this.get('props'),
                         id = this.get('id'),
                         friendlyName = props ? props.name : id,
                         labels = ['', friendlyName, id],
-                        idx = (nodeLabelIndex < labels.length) ? nodeLabelIndex : 0;
+                        nli = nodeLabelIndex,
+                        idx = (nli < labels.length) ? nli : 0;
 
                     return labels[idx];
                 },
-                trimLabel: function(label) {
+                trimLabel: function (label) {
                     return (label && label.trim()) || '';
                 },
-                computeLabelWidth: function(el) {
+                computeLabelWidth: function (el) {
                     var text = el.select('text'),
-                    box = text.node().getBBox();
+                        box = text.node().getBBox();
                     return box.width + labelPad * 2;
                 },
-                addLabelElements: function(label) {
+                addLabelElements: function (label) {
                     var rect = this.el.append('rect');
                     var text = this.el.append('text').text(label)
                         .attr('text-anchor', 'left')
@@ -146,12 +148,16 @@
                     return {
                         rect: rect,
                         text: text
-                    }
+                    };
                 },
                 svgClassName: function () {
-                    return fn.classNames('node', this.nodeType, this.get('type'), {
-                        online: this.get('online')
-                    });
+                    return fn.classNames('node',
+                        this.nodeType,
+                        this.get('type'),
+                        {
+                            online: this.get('online')
+                        }
+                    );
                 },
                 createNode: function () {
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
index 67fec91..3f6fa17 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
@@ -23,32 +23,30 @@
     'use strict';
 
     // Injected Services
-    var $log, wss, t2sr, t2ds, t2hs, t2ls;
-    var Collection, Model;
+    var $log, t2sr, t2ds, t2hs, t2ls;
+    var Model;
 
-    //Internal
+    // Internal
     var region;
 
-    function init() {
-        regions = {};
-    }
+    function init() {}
 
     function addRegion(data) {
 
         var RegionModel = Model.extend({
             findNodeById: findNodeById
-        })
+        });
 
         region = new RegionModel({
             id: data.id,
-            layerOrder: data.layerOrder,
+            layerOrder: data.layerOrder
         });
 
         region.set({
             subregions: t2sr.createSubRegionCollection(data.subregions, region),
             devices: t2ds.createDeviceCollection(data.devices, region),
             hosts: t2hs.createHostCollection(data.hosts, region),
-            links: t2ls.createLinkCollection(data.links, region),
+            links: t2ls.createLinkCollection(data.links, region)
         });
 
         angular.forEach(region.get('links').models, function (link) {
@@ -58,18 +56,10 @@
         $log.debug('Region: ', region);
     }
 
-    function regionNodes() {
-        return [].concat(
-            region.get('devices').models,
-            region.get('hosts').models,
-            region.get('subregions').models
-        );
-    }
-
     function findNodeById(id) {
 
         // Remove /{port} from id if needed
-        var regex = new RegExp('^[^\/]*');
+        var regex = new RegExp('^[^/]*');
         id = regex.exec(id)[0];
 
         return region.get('devices').get(id) ||
@@ -77,25 +67,37 @@
             region.get('subregions').get(id);
     }
 
+    function regionNodes() {
+
+        if (region) {
+            return [].concat(
+                region.get('devices').models,
+                region.get('hosts').models,
+                region.get('subregions').models
+            );
+        }
+
+        return [];
+    }
+
     function regionLinks() {
-        return region.get('links').models;
+        return (region) ? region.get('links').models : [];
     }
 
     angular.module('ovTopo2')
     .factory('Topo2RegionService',
-        ['$log', 'WebSocketService', 'Topo2Model', 'Topo2SubRegionService', 'Topo2DeviceService',
-        'Topo2HostService', 'Topo2LinkService', 'Topo2Collection',
+        ['$log', 'Topo2Model',
+        'Topo2SubRegionService', 'Topo2DeviceService',
+        'Topo2HostService', 'Topo2LinkService',
 
-        function (_$log_, _wss_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_, _Collection_) {
+        function (_$log_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) {
 
             $log = _$log_;
-            wss = _wss_;
-            Model = _Model_
+            Model = _Model_;
             t2sr = _t2sr_;
             t2ds = _t2ds_;
             t2hs = _t2hs_;
             t2ls = _t2ls_;
-            Collection = _Collection_;
 
             return {
                 init: init,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Select.js b/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
index 83d5195..073147d 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
@@ -22,26 +22,18 @@
     'use strict';
 
     // internal state
-    var hovered, selections, selectOrder, consumeClick;
+    var consumeClick;
 
-    function selectObject(obj) {
-        var el = this,
-            nodeEv = el && el.tagName === 'g',
-            ev = d3.event.sourceEvent || {},
-            n;
-
-        console.log(el, nodeEv, ev, n);
-    }
+    function selectObject(obj) {}
 
     function clickConsumed(x) {
         var cc = consumeClick;
-        consumeClick = !!x;
+        consumeClick = Boolean(x);
         return cc;
     }
 
     angular.module('ovTopo2')
-    .factory('Topo2SelectService',
-    [
+    .factory('Topo2SelectService', [
         function () {
 
             return {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
index 67de6cb..dad55f5 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
@@ -22,7 +22,7 @@
 (function () {
     'use strict';
 
-    var wss, is, sus, ts, t2vs;
+    var wss, is, sus;
     var Collection, Model;
 
     var remappedDeviceTypes = {
@@ -31,22 +31,7 @@
 
     // configuration
     var devIconDim = 36,
-        labelPad = 10,
-        hostRadius = 14,
-        badgeConfig = {
-            radius: 12,
-            yoff: 5,
-            gdelta: 10
-        },
-        halfDevIcon = devIconDim / 2,
-        devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
-        hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
-        status = {
-            i: 'badgeInfo',
-            w: 'badgeWarn',
-            e: 'badgeError'
-        },
-        deviceLabelIndex = 0;
+        halfDevIcon = devIconDim / 2;
 
     function createSubRegionCollection(data, region) {
 
@@ -67,22 +52,20 @@
             y: -dim / 2,
             width: dim + labelWidth,
             height: dim
-        }
+        };
     }
 
     angular.module('ovTopo2')
     .factory('Topo2SubRegionService',
-        ['WebSocketService', 'Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
-        'ThemeService', 'Topo2ViewService',
+        ['WebSocketService', 'Topo2Collection', 'Topo2NodeModel',
+        'IconService', 'SvgUtilService', 'ThemeService', 'Topo2ViewService',
 
-            function (_wss_, _Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
+            function (_wss_, _c_, _NodeModel_, _is_, _sus_, _ts_, _t2vs_) {
 
                 wss = _wss_;
-                t2vs = _t2vs_;
                 is = _is_;
                 sus = _sus_;
-                ts = _ts_;
-                Collection = _Collection_;
+                Collection = _c_;
 
                 Model = _NodeModel_.extend({
                     initialize: function () {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2View.js b/web/gui/src/main/webapp/app/view/topo2/topo2View.js
index e856a1f..9178e1a 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2View.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2View.js
@@ -39,7 +39,7 @@
                 return {
                     newDim: newDim,
                     getDimensions: getDimensions
-                }
+                };
             }
         ]
     );
diff --git a/web/gui/src/main/webapp/package.json b/web/gui/src/main/webapp/package.json
index fb7cf86..afe0ebe 100644
--- a/web/gui/src/main/webapp/package.json
+++ b/web/gui/src/main/webapp/package.json
@@ -15,8 +15,10 @@
   "license": "Apache 2.0",
   "devDependencies": {
     "browser-sync": "^2.12.8",
+    "eslint": "^3.4.0",
+    "eslint-config-google": "^0.6.0",
+    "express": "^4.14.0",
     "parallelshell": "^2.0.0",
-    "serve-static": "^1.10.2",
-    "express": "^4.14.0"
+    "serve-static": "^1.10.2"
   }
 }