Update GUI2 topology host icons

Change-Id: I6d74de9df93b91eb9ca126ab54cbc2912c16caff
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'));
     }
 }