Update GUI2 topology host icons

Change-Id: I6d74de9df93b91eb9ca126ab54cbc2912c16caff
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
index 6a900fb..5b801ab 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
@@ -42,6 +42,9 @@
 
     ['m_ports', 'm_ports'],
     ['m_switch', 'm_switch'],
+    ['m_roadm', 'm_roadm'],
+    ['m_router', 'm_router'],
+    ['m_endstation', 'm_endstation'],
 
     ['topo', 'topo'],
     ['bird', 'bird'],
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index 7cf1766..9e975be 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -4705,7 +4705,7 @@
     },
     "fm-gui2-lib": {
       "version": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-2.0.0.tgz",
-      "integrity": "sha512-AYRLoCF5jSYYAvi6fYJCepKtCm9Woregjte79YTQvgzL/+2AvTtXf9m2ezj/wmUnZOfFH6wTqVdW1OLzK/TQiQ==",
+      "integrity": "sha512-4t2yYPn5gA2+2UIJvecu1konQkWe67giLToloF23LmbjKFLgdxOslL22hL3lpvIHUr2xNmKAO0zUt10vQFRS2w==",
       "requires": {
         "tslib": "1.9.3"
       }
@@ -5560,7 +5560,7 @@
     },
     "gui2-fw-lib": {
       "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
-      "integrity": "sha512-wAhrApE0c7FWqRDgB/8itVppmsd1Z4T+j8woxmDzkYwIkEqSvc7pvF3+QIOF0IQDkRCU2P31Ms1AtZ5cQliXTA==",
+      "integrity": "sha512-Yzq0PJtfLjiEM0/U9MYD17RMAEYrnbj+m24urMEJwgOZcn9502ZcmlJInl04gAxxGtU0GxlScAYh9TfxW1ZpCw==",
       "requires": {
         "tslib": "1.9.3"
       }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
index f02b3a1..1f1d757 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
@@ -16,20 +16,55 @@
 <svg:g class="topo2-force" xmlns:svg="http://www.w3.org/2000/svg">
     <svg:g id="new-zoom-layer">
         <svg:g class="topo2-links">
-            <svg:g onos-linkvisual [link]="link" *ngFor="let link of regionData.links">
+            <!-- Template explanation: Creates an SVG Group and in
+                line 1) use the svg component onos-linkvisual, setting it's link
+                 Input parameter to the link item from the next line
+                line 2) Use the built in NgFor directive to iterate through the
+                 set of links filtered by the filteredLinks() function.
+            -->
+            <svg:g onos-linkvisual [link]="link"
+                   *ngFor="let link of filteredLinks()">
             </svg:g>
         </svg:g>
         <svg:g class="topo2-linkLabels" />
         <svg:g class="topo2-numLinkLabels" />
         <svg:g class="topo2-nodes">
+            <!-- Template explanation - create an SVG Group and
+                line 1) use the svg component onos-devicenodesvg, setting it's device
+                 Input parameter to the device item from the next line
+                line 2) Use the built in NgFor directive to iterate through all
+                 of the devices in the chosen layer index. The current iteration
+                 is in the device variable
+                line 3) Use the onosDraggable directive and pass this device in to
+                 its draggableNode Input parameter and setting the draggableInGraph
+                 Input parameter to 'graph'
+                line 4) when the onos-devicenodesvg component emits the selectedEvent
+                 call the updateSelected() method of this (forcesvg) component
+            -->
             <svg:g onos-devicenodesvg [device]="device"
                    *ngFor="let device of regionData.devices[visibleLayerIdx()]"
                    onosDraggableNode [draggableNode]="device" [draggableInGraph]="graph"
                    (selectedEvent)="updateSelected($event)">
             </svg:g>
-            <svg:g onos-hostnodesvg [host]="host"
-                   *ngFor="let host of regionData.hosts[visibleLayerIdx()]"
-                   onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph">
+            <!-- Template explanation - only display the hosts if 'showHosts' is set true -->
+            <svg:g *ngIf="showHosts">
+                <!-- Template explanation - create an SVG Group and
+                    line 1) use the svg component onos-hostnodesvg, setting it's host
+                     Input parameter to the host item from the next line
+                    line 2) Use the built in NgFor directive to iterate through all
+                     of the hosts in the chosen layer index. The current iteration
+                     is in the 'host' variable
+                    line 3) Use the onosDraggable directive and pass this host in to
+                     its draggableNode Input parameter and setting the draggableInGraph
+                     Input parameter to 'graph'
+                    line 4) when the onos-hostnodesvg component emits the selectedEvent
+                     call the updateSelected() method of this (forcesvg) component
+                -->
+                <svg:g onos-hostnodesvg [host]="host"
+                       *ngFor="let host of regionData.hosts[visibleLayerIdx()]"
+                       onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph"
+                       (selectedEvent)="updateSelected($event)">
+                </svg:g>
             </svg:g>
             <svg:g onos-subregionnodesvg [subRegion]="subRegion"
                    *ngFor="let subRegion of regionData.subregions"
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
index 97c9e1e..1d6ebf4 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
@@ -22,21 +22,24 @@
     Input,
     OnChanges,
     OnInit,
-    Output, QueryList, SimpleChange,
-    SimpleChanges, ViewChildren
+    Output,
+    QueryList,
+    SimpleChanges,
+    ViewChildren
 } from '@angular/core';
 import {IconService, LogService} from 'gui2-fw-lib';
 import {
     Device,
     ForceDirectedGraph,
-    Host,
+    Host, HostLabelToggle,
     LabelToggle,
     LayerType,
+    Node,
     Region,
     RegionLink,
     SubRegion
 } from './models';
-import {DeviceNodeSvgComponent} from './visuals';
+import {DeviceNodeSvgComponent, HostNodeSvgComponent} from './visuals';
 
 
 /**
@@ -55,20 +58,19 @@
     @Input() onosInstMastership: string = '';
     @Input() visibleLayer: LayerType = LayerType.LAYER_DEFAULT;
     @Output() linkSelected = new EventEmitter<RegionLink>();
-    @Output() selectedNodeEvent = new EventEmitter<Device>();
+    @Output() selectedNodeEvent = new EventEmitter<Node>();
     @Input() selectedLink: RegionLink = null;
-    private graph: ForceDirectedGraph;
-
+    @Input() showHosts: boolean = false;
+    @Input() deviceLabelToggle: LabelToggle = LabelToggle.NONE;
+    @Input() hostLabelToggle: HostLabelToggle = HostLabelToggle.NONE;
     @Input() regionData: Region = <Region>{devices: [ [], [], [] ], hosts: [ [], [], [] ], links: []};
+    private graph: ForceDirectedGraph;
     private _options: { width, height } = { width: 800, height: 600 };
 
+    // References to the children of this component - these are created in the
+    // template view with the *ngFor and we get them by a query here
     @ViewChildren(DeviceNodeSvgComponent) devices: QueryList<DeviceNodeSvgComponent>;
-
-    @HostListener('window:resize', ['$event'])
-    onResize(event) {
-        this.graph.initSimulation(this.options);
-        this.log.debug('Simulation reinit after resize', event);
-    }
+    @ViewChildren(HostNodeSvgComponent) hosts: QueryList<HostNodeSvgComponent>;
 
     constructor(
         protected log: LogService,
@@ -80,16 +82,47 @@
     }
 
     /**
+     * Utility for extracting a node name from an endpoint string
+     * In some cases - have to remove the port number from the end of a device
+     * name
+     * @param endPtStr The end point name
+     */
+    private static extractNodeName(endPtStr: string): string {
+        const slash: number = endPtStr.indexOf('/');
+        if (slash === -1) {
+            return endPtStr;
+        } else {
+            const afterSlash = endPtStr.substr(slash + 1);
+            if (afterSlash === 'None') {
+                return endPtStr;
+            } else {
+                return endPtStr.substr(0, slash);
+            }
+        }
+    }
+
+    @HostListener('window:resize', ['$event'])
+    onResize(event) {
+        this.graph.initSimulation(this.options);
+        this.log.debug('Simulation reinit after resize', event);
+    }
+
+    /**
      * After the component is initialized create the Force simulation
+     * The list of devices, hosts and links will not have been receieved back
+     * from the WebSocket yet as this time - they will be updated later through
+     * ngOnChanges()
      */
     ngOnInit() {
         // Receiving an initialized simulated graph from our custom d3 service
         this.graph = new ForceDirectedGraph(this.options);
 
         /** Binding change detection check on each tick
-         * This along with an onPush change detection strategy should enforce checking only when relevant!
-         * This improves scripting computation duration in a couple of tests I've made, consistently.
-         * Also, it makes sense to avoid unnecessary checks when we are dealing only with simulations data binding.
+         * This along with an onPush change detection strategy should enforce
+         * checking only when relevant! This improves scripting computation
+         * duration in a couple of tests I've made, consistently. Also, it makes
+         * sense to avoid unnecessary checks when we are dealing only with
+         * simulations data binding.
          */
         this.graph.ticker.subscribe((simulation) => {
             // this.log.debug("Force simulation has ticked", simulation);
@@ -98,12 +131,16 @@
         this.log.debug('ForceSvgComponent initialized - waiting for nodes and links');
 
         this.is.loadIconDef('m_switch');
+        this.is.loadIconDef('m_roadm');
+        this.is.loadIconDef('m_router');
+        this.is.loadIconDef('m_endstation');
     }
 
     /**
-     * When any one of the inputs get changed by a containing component, this gets called automatically
-     * In addition this is called manually by topology.service when a response
-     * is received from the WebSocket from the server
+     * When any one of the inputs get changed by a containing component, this
+     * gets called automatically. In addition this is called manually by
+     * topology.service when a response is received from the WebSocket from the
+     * server
      *
      * The Devices, Hosts and SubRegions are all added to the Node list for the simulation
      * The Links are added to the Link list of the simulation.
@@ -132,12 +169,16 @@
             // Associate the endpoints of each link with a real node
             this.graph.links = [];
             for (const linkIdx of Object.keys(this.regionData.links)) {
+                const epA = ForceSvgComponent.extractNodeName(
+                                        this.regionData.links[linkIdx].epA);
                 this.regionData.links[linkIdx].source =
                     this.graph.nodes.find((node) =>
-                        node.id === this.regionData.links[linkIdx].epA);
+                        node.id === epA);
+                const epB = ForceSvgComponent.extractNodeName(
+                    this.regionData.links[linkIdx].epB);
                 this.regionData.links[linkIdx].target =
                     this.graph.nodes.find((node) =>
-                        node.id === this.regionData.links[linkIdx].epB);
+                        node.id === epB);
                 this.regionData.links[linkIdx].index = Number(linkIdx);
             }
 
@@ -148,6 +189,28 @@
             this.log.debug('ForceSvgComponent input changed',
                 this.graph.nodes.length, 'nodes,', this.graph.links.length, 'links');
         }
+
+        if (changes['showHosts']) {
+            this.showHosts = changes['showHosts'].currentValue;
+        }
+
+        // Pass on the changes to device
+        if (changes['deviceLabelToggle']) {
+            this.deviceLabelToggle = changes['deviceLabelToggle'].currentValue;
+            this.devices.forEach((d) => {
+                d.ngOnChanges({'labelToggle': changes['deviceLabelToggle']});
+            });
+        }
+
+        // Pass on the changes to host
+        if (changes['hostLabelToggle']) {
+            this.hostLabelToggle = changes['hostLabelToggle'].currentValue;
+            this.hosts.forEach((h) => {
+                h.ngOnChanges({'labelToggle': changes['hostLabelToggle']});
+            });
+        }
+
+        this.ref.markForCheck();
     }
 
     /**
@@ -176,26 +239,36 @@
         };
     }
 
-    updateDeviceLabelToggle() {
-        this.devices.forEach((d) => {
-            const old: LabelToggle = d.labelToggle;
-            const next = LabelToggle.next(old);
-            d.ngOnChanges({'labelToggle': new SimpleChange(old, next, false)});
-        });
+    /**
+     * Iterate through all hosts and devices to deselect the previously selected
+     * node. The emit an event to the parent that lets it know the selection has
+     * changed.
+     * @param selectedNode the newly selected node
+     */
+    updateSelected(selectedNode: Node): void {
+        this.log.debug('Device selected', selectedNode);
+        this.devices
+            .filter((d) =>
+                selectedNode === undefined || d.device.id !== selectedNode.id)
+            .forEach((d) => d.deselect());
+        this.hosts
+            .filter((h) =>
+                selectedNode === undefined || h.host.id !== selectedNode.id)
+            .forEach((h) => h.deselect());
+
+        this.selectedNodeEvent.emit(selectedNode);
     }
 
-    updateSelected(selectedNodeId: string): void {
-        this.log.debug('Device selected', selectedNodeId);
-        this.devices.filter((d) => d.device.id !== selectedNodeId).forEach((d) => {
-            d.deselect();
-        });
-        const selectedDevice: DeviceNodeSvgComponent =
-            (this.devices.find((d) => d.device.id === selectedNodeId));
-        if (selectedDevice) {
-            this.selectedNodeEvent.emit(selectedDevice.device);
-        } else {
-            this.selectedNodeEvent.emit();
-        }
+    /**
+     * We want to filter links to show only those not related to hosts if the
+     * 'showHosts' flag has been switched off. If 'shwoHosts' is true, then
+     * display all links.
+     */
+    filteredLinks() {
+        return this.regionData.links.filter((h) =>
+            this.showHosts ||
+            ((<Host>h.source).nodeType !== 'host' &&
+            (<Host>h.target).nodeType !== 'host'));
     }
 }
 
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
index 3db25ae..46d3ba7 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
@@ -21,7 +21,7 @@
 const FORCES = {
     LINKS: 1 / 50,
     COLLISION: 1,
-    CHARGE: -1
+    CHARGE: -10
 };
 
 export interface Options {
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
index d19de88..a7a7cb1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
@@ -23,7 +23,7 @@
 } from './regions';
 
 /**
- * Toggle state for how labels should be displayed
+ * Toggle state for how device labels should be displayed
  */
 export enum LabelToggle {
     NONE,
@@ -31,6 +31,9 @@
     NAME
 }
 
+/**
+ * Add the method 'next()' to the LabelToggle enum above
+ */
 export namespace LabelToggle {
     export function next(current: LabelToggle) {
         if (current === LabelToggle.NONE) {
@@ -44,6 +47,33 @@
 }
 
 /**
+ * Toggle state for how host labels should be displayed
+ */
+export enum HostLabelToggle {
+    NONE,
+    NAME,
+    IP,
+    MAC
+}
+
+/**
+ * Add the method 'next()' to the HostLabelToggle enum above
+ */
+export namespace HostLabelToggle {
+    export function next(current: HostLabelToggle) {
+        if (current === HostLabelToggle.NONE) {
+            return HostLabelToggle.NAME;
+        } else if (current === HostLabelToggle.NAME) {
+            return HostLabelToggle.IP;
+        } else if (current === HostLabelToggle.IP) {
+            return HostLabelToggle.MAC;
+        } else if (current === HostLabelToggle.MAC) {
+            return HostLabelToggle.NONE;
+        }
+    }
+}
+
+/**
  * model of the topo2CurrentRegion device props from Device below
  */
 export interface DeviceProps {
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
index d00e906..e329874 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
@@ -16,7 +16,7 @@
 <svg:g xmlns:svg="http://www.w3.org/2000/svg" #devSvg
        [attr.transform]="'translate(' + device?.x + ',' + device?.y + '), scale(' + scale + ')'"
         [ngClass]="['node', 'device', device.online?'online':'', selected?'selected':'']"
-        (click)="toggleSelected()">
+        (click)="toggleSelected(device)">
     <svg:rect class="node-container" x="-18" y="-18" width="36" height="36">
               <!--[attr.width]="devText.getComputedTextLength()+36" -->
               <!--[@deviceLabelToggle]="{ value: labelToggle, params: {txtWidth: devSvg.getBBox().width+'px' }}">-->
@@ -26,5 +26,9 @@
     <svg:text #devText text-anchor="start" y="0.3em" x="22" [ngStyle]="{'transform': 'scale(' + scale + ')'}">
         {{ labelToggle == 0 ? '': labelToggle == 1 ? device.id:device.props.name }}
     </svg:text>
-    <svg:use xlink:href="#m_switch" width="36" height="36" x="-18" y="-18"></svg:use>
+    <!-- It's not possible to drive the following xref dynamically -->
+    <svg:use *ngIf="device.type == 'switch'" xlink:href="#m_switch" width="36" height="36" x="-18" y="-18">
+    </svg:use>
+    <svg:use *ngIf="device.type == 'roadm'" xlink:href="#m_roadm" width="36" height="36" x="-18" y="-18">
+    </svg:use>
 </svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
index bec4529..036fc08 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
@@ -22,8 +22,9 @@
     SimpleChanges,
     ViewChild
 } from '@angular/core';
-import {Device, LabelToggle} from '../../models';
+import {Node, Device, LabelToggle} from '../../models';
 import {LogService} from 'gui2-fw-lib';
+import {NodeVisual} from '../nodevisual';
 
 /**
  * The Device node in the force graph
@@ -50,19 +51,18 @@
     //     ])
     // ]
 })
-export class DeviceNodeSvgComponent implements OnChanges {
+export class DeviceNodeSvgComponent extends NodeVisual implements OnChanges {
     @Input() device: Device;
     @Input() scale: number = 1.0;
     @Input() labelToggle: LabelToggle = LabelToggle.NONE;
-    selected: boolean;
-    @Output() selectedEvent = new EventEmitter<string>();
+    @Output() selectedEvent = new EventEmitter<Node>();
     textWidth: number = 36;
     @ViewChild('idTxt') idTxt: ElementRef;
-
     constructor(
         protected log: LogService,
         private ref: ChangeDetectorRef
     ) {
+        super();
     }
 
     /**
@@ -84,18 +84,4 @@
         }
         this.ref.markForCheck();
     }
-
-    toggleSelected() {
-        this.selected = !this.selected;
-        if (this.selected) {
-            this.selectedEvent.emit(this.device.id);
-        } else {
-            this.selectedEvent.emit();
-        }
-    }
-
-    deselect() {
-        this.selected = false;
-    }
-
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
index 60cedab..92a114f 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
@@ -17,4 +17,27 @@
 
 /*
  ONOS GUI -- Topology View (forces host visual) -- CSS file
- */
\ No newline at end of file
+ */
+.node.host text {
+    stroke: none;
+    font-size: 13px;
+    fill: #846;
+}
+
+.node.host circle {
+    stroke: #a3a596;
+    fill: #e0dfd6;
+}
+
+.node.host.selected > circle {
+    stroke-width: 2.0;
+    stroke: #009fdb;
+}
+
+.node.host use {
+    fill: #3c3a3a;
+}
+
+.node.host rect {
+    fill: #ffffff;
+}
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
index 8626cf5..9cac8b0 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
@@ -13,11 +13,11 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:g  xmlns:svg="http://www.w3.org/2000/svg" [attr.transform]="'translate(' + host?.x + ',' + host?.y + ')'">>
-  <svg:circle
-          cx="0"
-          cy="0"
-          r="5">
-  </svg:circle>
-  <svg:text>{{host?.id}}</svg:text>
+<svg:g  xmlns:svg="http://www.w3.org/2000/svg"
+        [attr.transform]="'translate(' + host?.x + ',' + host?.y + '), scale(' + scale + ')'"
+        [ngClass]="['node', 'host', 'endstation', 'fixed', selected?'selected':'', 'hovered']"
+        (click)="toggleSelected(host)">
+    <svg:circle r="15"></svg:circle>
+    <svg:use xlink:href="#m_endstation" width="22.5" height="22.5" x="-11.25" y="-11.25" style="transform: scale(1);"></svg:use>
+    <svg:text *ngIf="labelToggle != 0" dy="24" text-anchor="middle" style="transform: scale(1);">{{hostName()}}</svg:text>
 </svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
index 4b1f4ab..0720d0e 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
@@ -15,32 +15,37 @@
  */
 import {
     Component,
+    EventEmitter,
     Input,
     OnChanges,
+    Output,
     SimpleChanges
 } from '@angular/core';
-import { Host } from '../../models';
+import {Host, HostLabelToggle, Node} from '../../models';
 import {LogService} from 'gui2-fw-lib';
+import {NodeVisual} from '../nodevisual';
 
 /**
  * The Host node in the force graph
  *
- * Note 1: here the selector is given square brackets [] so that it can be
+ * Note: here the selector is given square brackets [] so that it can be
  * inserted in SVG element like a directive
- * Note 2: the selector is exactly the same as the @Input alias to make this
- * directive trick work
  */
 @Component({
     selector: '[onos-hostnodesvg]',
     templateUrl: './hostnodesvg.component.html',
     styleUrls: ['./hostnodesvg.component.css']
 })
-export class HostNodeSvgComponent implements OnChanges {
+export class HostNodeSvgComponent extends NodeVisual implements OnChanges {
     @Input() host: Host;
+    @Input() scale: number = 1.0;
+    @Input() labelToggle: HostLabelToggle = HostLabelToggle.IP;
+    @Output() selectedEvent = new EventEmitter<Node>();
 
     constructor(
         protected log: LogService
     ) {
+        super();
     }
 
     ngOnChanges(changes: SimpleChanges) {
@@ -48,5 +53,23 @@
             this.host.x = 0;
             this.host.y = 0;
         }
+
+        if (changes['labelToggle']) {
+            this.labelToggle = changes['labelToggle'].currentValue;
+        }
+        // this.ref.markForCheck();
+    }
+
+    hostName(): string {
+        if (this.host === undefined) {
+            return undefined;
+        } else if (this.labelToggle === HostLabelToggle.IP) {
+            return this.host.ips.join(',');
+        } else if (this.labelToggle === HostLabelToggle.MAC) {
+            return this.host.id;
+        } else {
+            return this.host.id; // Todo - replace with a friendly name
+        }
+
     }
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/nodevisual.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/nodevisual.ts
new file mode 100644
index 0000000..677745d
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/nodevisual.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+import {EventEmitter} from '@angular/core';
+import {Node} from '../models';
+
+export abstract class NodeVisual {
+    selected: boolean;
+    selectedEvent = new EventEmitter<Node>();
+
+    toggleSelected(node: Node) {
+        this.selected = !this.selected;
+        if (this.selected) {
+            this.selectedEvent.emit(node);
+        } else {
+            this.selectedEvent.emit();
+        }
+    }
+
+    deselect() {
+        this.selected = false;
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
index d4decb0..433f749 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
@@ -16,7 +16,7 @@
 import {
     Component,
     OnDestroy,
-    OnInit,
+    OnInit, SimpleChange,
     ViewChild
 } from '@angular/core';
 import * as d3 from 'd3';
@@ -38,7 +38,7 @@
 import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
 import {ForceSvgComponent} from '../layer/forcesvg/forcesvg.component';
 import {TopologyService} from '../topology.service';
-import {Node} from '../layer/forcesvg/models';
+import {HostLabelToggle, LabelToggle, Node} from '../layer/forcesvg/models';
 
 /**
  * ONOS GUI Topology View
@@ -98,6 +98,23 @@
         this.log.debug('Topology component constructed');
     }
 
+    private static deviceLabelFlashMessage(index: number): string {
+        switch (index) {
+            case 0: return 'Hide device labels';
+            case 1: return 'Show friendly device labels';
+            case 2: return 'Show device ID labels';
+        }
+    }
+
+    private static hostLabelFlashMessage(index: number): string {
+        switch (index) {
+            case 0: return 'Hide host labels';
+            case 1: return 'Show friendly host labels';
+            case 2: return 'Show host IP labels';
+            case 3: return 'Show host MAC Address labels';
+        }
+    }
+
     ngOnInit() {
         this.bindCommands();
         this.zoomer = this.createZoomer(<ZoomOpts>{
@@ -228,43 +245,22 @@
         this.ps.setPrefs('topo2_prefs', this.prefsState);
     }
 
-    deviceLabelFlashMessage(index) {
-        switch (index) {
-            case 0: return 'Hide device labels';
-            case 1: return 'Show friendly device labels';
-            case 2: return 'Show device ID labels';
-        }
-    }
-
-    hostLabelFlashMessage(index) {
-        switch (index) {
-            case 0: return 'Hide host labels';
-            case 1: return 'Show friendly host labels';
-            case 2: return 'Show host IP labels';
-            case 3: return 'Show host MAC Address labels';
-        }
-    }
-
     protected cycleDeviceLabels() {
-        this.log.debug('Cycling device labels');
-        // TODO: Reinstate with components
-        this.force.updateDeviceLabelToggle();
-        // let deviceLabelIndex = t2ps.get('dlbls') + 1;
-        // let newDeviceLabelIndex = deviceLabelIndex % 3;
-        //
-        // t2ps.set('dlbls', newDeviceLabelIndex);
-        // t2fs.updateNodes();
-        // flash.flash(deviceLabelFlashMessage(newDeviceLabelIndex));
+        const old: LabelToggle = this.force.deviceLabelToggle;
+        const next = LabelToggle.next(old);
+        this.force.ngOnChanges({'deviceLabelToggle':
+                new SimpleChange(old, next, false)});
+        this.flashMsg = TopologyComponent.deviceLabelFlashMessage(next);
+        this.log.debug('Cycling device labels', old, next);
     }
 
     protected cycleHostLabels() {
-        const hostLabelIndex = this.hostLabelIdx + 1;
-        this.hostLabelIdx = hostLabelIndex % 4;
-        this.flashMsg = this.hostLabelFlashMessage(this.hostLabelIdx);
-        this.log.debug('Cycling host labels');
-        // TODO: Reinstate with components
-        // t2ps.set('hlbls', newHostLabelIndex);
-        // t2fs.updateNodes();
+        const old: HostLabelToggle = this.force.hostLabelToggle;
+        const next = HostLabelToggle.next(old);
+        this.force.ngOnChanges({'hostLabelToggle':
+                new SimpleChange(old, next, false)});
+        this.flashMsg = TopologyComponent.hostLabelFlashMessage(next);
+        this.log.debug('Cycling host labels', old, next);
     }
 
     protected toggleBackground(token: KeysToken) {
@@ -346,11 +342,11 @@
     }
 
     protected toggleHosts() {
-        // this.flashMsg = on ? 'Show': 'Hide', 'Hosts';
-        this.log.debug('toggling hosts');
-        // TODO: Reinstate with components
-        // let on = t2rs.toggleHosts();
-        // this.actionedFlashed(on ? 'Show': 'Hide', 'Hosts');
+        const old: boolean = this.force.showHosts;
+        const current = !this.force.showHosts;
+        this.force.ngOnChanges({'showHosts': new SimpleChange(old, current, false)});
+        this.flashMsg = (this.force.showHosts ? 'Show' : 'Hide') + ' Hosts';
+        this.log.debug('toggling hosts: ', this.force.showHosts ? 'Show' : 'Hide');
     }
 
     protected toggleOfflineDevices() {