GUI2 bug fix for Force graph

Change-Id: I6f47f22327ec115a7a59dd8a6d685f74fccfbe5f
diff --git a/web/gui2-topo-lib/package-lock.json b/web/gui2-topo-lib/package-lock.json
index 9152a2d..dd709c7 100644
--- a/web/gui2-topo-lib/package-lock.json
+++ b/web/gui2-topo-lib/package-lock.json
@@ -4873,7 +4873,7 @@
     },
     "gui2-fw-lib": {
       "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.1.0.tgz",
-      "integrity": "sha512-UF/S6N4eNo3lrIRE9B/5WrGvq84tyfeClW3TKj2ftbTXpoNSiNcjLWYHiueR8dqPhh68AeEx97iVXkg9hU7BkQ==",
+      "integrity": "sha512-wcyiMBITpdVE+EmiN/uZWfHq3CK5kwmG+Os2xsmp3mJsslZSnK7f4taCUQkh0YXSxqpfGAi0xT+FCFIag/8SaQ==",
       "requires": {
         "tslib": "1.9.3"
       }
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.spec.ts
index 9a2ae0e..aa9ce64 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.spec.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.spec.ts
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
 
-import { ForceSvgComponent } from './forcesvg.component';
+import {ForceSvgComponent} from './forcesvg.component';
 import {FnService, LogService} from 'gui2-fw-lib';
 import {DraggableDirective} from './draggable/draggable.directive';
 import {ActivatedRoute, Params} from '@angular/router';
@@ -25,6 +25,8 @@
 import {SubRegionNodeSvgComponent} from './visuals/subregionnodesvg/subregionnodesvg.component';
 import {HostNodeSvgComponent} from './visuals/hostnodesvg/hostnodesvg.component';
 import {LinkSvgComponent} from './visuals/linksvg/linksvg.component';
+import {Device, Host, Link, LinkType, Region} from './models';
+import {SimpleChange} from '@angular/core';
 
 class MockActivatedRoute extends ActivatedRoute {
     constructor(params: Params) {
@@ -40,6 +42,9 @@
     let logServiceSpy: jasmine.SpyObj<LogService>;
     let component: ForceSvgComponent;
     let fixture: ComponentFixture<ForceSvgComponent>;
+    const sampledata = require('./tests/test-module-topo2CurrentRegion.json');
+    const regionData: Region = <Region><unknown>(sampledata.payload);
+    const emptyRegion: Region = <Region>{devices: [ [], [], [] ], hosts: [ [], [], [] ], links: []};
 
     beforeEach(async(() => {
         const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
@@ -88,4 +93,106 @@
     it('should create', () => {
         expect(component).toBeTruthy();
     });
+
+    it('load sample file', () => {
+        expect(sampledata).toBeTruthy();
+        expect(sampledata.payload).toBeTruthy();
+        expect(sampledata.payload.id).toBe('(root)');
+    });
+
+    it('should read sample data payload as Region', () => {
+        expect(regionData).toBeTruthy();
+        // console.log(regionData);
+        expect(regionData.id).toBe('(root)');
+        expect(regionData.devices).toBeTruthy();
+        expect(regionData.devices.length).toBe(3);
+        expect(regionData.devices[2].length).toBe(10);
+        expect(regionData.hosts.length).toBe(3);
+        expect(regionData.hosts[2].length).toBe(20);
+        expect(regionData.links.length).toBe(44);
+    });
+
+    it('should read device246 correctly', () => {
+        const device246: Device = regionData.devices[2][0];
+        expect(device246.id).toBe('of:0000000000000246');
+        expect(device246.nodeType).toBe('device');
+        expect(device246.type).toBe('switch');
+        expect(device246.online).toBe(true);
+        expect(device246.master).toBe('10.192.19.68');
+        expect(device246.layer).toBe('def');
+
+        expect(device246.props.managementAddress).toBe('10.192.19.69');
+        expect(device246.props.protocol).toBe('OF_13');
+        expect(device246.props.driver).toBe('ofdpa-ovs');
+        expect(device246.props.latitude).toBe('40.15');
+        expect(device246.props.name).toBe('s246');
+        expect(device246.props.locType).toBe('geo');
+        expect(device246.props.channelId).toBe('10.192.19.69:59980');
+        expect(device246.props.longitude).toBe('-121.679');
+
+        expect(device246.location.locType).toBe('geo');
+        expect(device246.location.latOrY).toBe(40.15);
+        expect(device246.location.longOrX).toBe(-121.679);
+    });
+
+    it('should read host 3 correctly', () => {
+        const host3: Host = regionData.hosts[2][0];
+        expect(host3.id).toBe('00:88:00:00:00:03/110');
+        expect(host3.nodeType).toBe('host');
+        expect(host3.layer).toBe('def');
+        expect(host3.configured).toBe(false);
+        expect(host3.ips.length).toBe(3);
+        expect(host3.ips[0]).toBe('fe80::288:ff:fe00:3');
+        expect(host3.ips[1]).toBe('2000::102');
+        expect(host3.ips[2]).toBe('10.0.1.2');
+    });
+
+    it('should read link 3-205 correctly', () => {
+        const link3_205: Link = regionData.links[0];
+        expect(link3_205.id).toBe('00:AA:00:00:00:03/None~of:0000000000000205/6');
+        expect(link3_205.epA).toBe('00:AA:00:00:00:03/None');
+        expect(link3_205.epB).toBe('of:0000000000000205');
+        expect(String(LinkType[link3_205.type])).toBe('2');
+        expect(link3_205.portA).toBe(undefined);
+        expect(link3_205.portB).toBe('6');
+
+        expect(link3_205.rollup).toBeTruthy();
+        expect(link3_205.rollup.length).toBe(1);
+        expect(link3_205.rollup[0].id).toBe('00:AA:00:00:00:03/None~of:0000000000000205/6');
+        expect(link3_205.rollup[0].epA).toBe('00:AA:00:00:00:03/None');
+        expect(link3_205.rollup[0].epB).toBe('of:0000000000000205');
+        expect(String(LinkType[link3_205.rollup[0].type])).toBe('2');
+        expect(link3_205.rollup[0].portA).toBe(undefined);
+        expect(link3_205.rollup[0].portB).toBe('6');
+
+    });
+
+    it('should handle regionData change - empty Region', () => {
+        component.ngOnChanges(
+            {'regionData' : new SimpleChange(<Region>{}, emptyRegion, true)});
+
+        expect(component.graph.nodes.length).toBe(0);
+    });
+
+    it('should know hwo to format names', () => {
+        expect(ForceSvgComponent.extractNodeName('00:AA:00:00:00:03/None'))
+            .toEqual('00:AA:00:00:00:03/None');
+
+        expect(ForceSvgComponent.extractNodeName('00:AA:00:00:00:03/161'))
+            .toEqual('00:AA:00:00:00:03/161');
+
+        expect(ForceSvgComponent.extractNodeName('of:0000000000000206/6'))
+            .toEqual('of:0000000000000206');
+    });
+
+    it('should handle regionData change - sample Region', () => {
+        component.regionData = regionData;
+        component.ngOnChanges(
+            {'regionData' : new SimpleChange(<Region>{}, regionData, true)});
+
+        expect(component.graph.nodes.length).toBe(30);
+
+        expect(component.graph.links.length).toBe(44);
+
+    });
 });
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
index af0ca2b..d6a9148 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
@@ -119,14 +119,17 @@
      * name
      * @param endPtStr The end point name
      */
-    private static extractNodeName(endPtStr: string): string {
+    static extractNodeName(endPtStr: string): string {
         const slash: number = endPtStr.indexOf('/');
         if (slash === -1) {
             return endPtStr;
         } else {
             const afterSlash = endPtStr.substr(slash + 1);
+            const beforeSlash = endPtStr.substr(0, slash);
             if (afterSlash === 'None') {
                 return endPtStr;
+            } else if (beforeSlash.split(':').length > 2) {
+                return endPtStr; // Host name with mac address
             } else {
                 return endPtStr.substr(0, slash);
             }
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/link.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/link.ts
index 7192de0..d5ff2a7 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/link.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/link.ts
@@ -47,6 +47,7 @@
     portA: string; // The number of the port at one end
     portB: string; // The number of the port at the other end
     type: LinkType;
+    rollup: RegionRollup[]; // Links in sub regions represented by this one link
 
     // Must - defining enforced implementation properties
     source: Node;
@@ -96,7 +97,6 @@
  * model of the topo2CurrentRegion region link from Region
  */
 export class RegionLink extends Link {
-    rollup: RegionRollup[]; // Links in sub regions represented by this one link
 
     constructor(type: LinkType, nodeA: Node, nodeB: Node) {
         super(nodeA, nodeB);
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts
index 1402f81..ccd9250 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/models/node.ts
@@ -105,8 +105,8 @@
  * model of the topo2CurrentRegion device props from Device below
  */
 export interface DeviceProps {
-    latitude: number;
-    longitude: number;
+    latitude: string;
+    longitude: string;
     gridX: number;
     gridY: number;
     name: string;
@@ -115,6 +115,7 @@
     channelId: string;
     managementAddress: string;
     protocol: string;
+    driver: string;
 }
 
 export interface HostProps {
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/tests/test-module-topo2CurrentRegion.json b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/tests/test-module-topo2CurrentRegion.json
new file mode 100644
index 0000000..e8af22f
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/tests/test-module-topo2CurrentRegion.json
@@ -0,0 +1,1204 @@
+{
+  "event": "topo2CurrentRegion",
+  "payload": {
+    "id": "(root)",
+    "subregions": [],
+    "links": [
+      {
+        "id": "00:AA:00:00:00:03/None~of:0000000000000205/6",
+        "epA": "00:AA:00:00:00:03/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "6",
+        "rollup": [
+          {
+            "id": "00:AA:00:00:00:03/None~of:0000000000000205/6",
+            "epA": "00:AA:00:00:00:03/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "6"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000205/3~of:0000000000000227/5",
+        "epA": "of:0000000000000205/3",
+        "epB": "of:0000000000000227/5",
+        "type": "UiDeviceLink",
+        "portA": "3",
+        "portB": "5",
+        "rollup": [
+          {
+            "id": "of:0000000000000205/3~of:0000000000000227/5",
+            "epA": "of:0000000000000205/3",
+            "epB": "of:0000000000000227/5",
+            "type": "UiDeviceLink",
+            "portA": "3",
+            "portB": "5"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000206/2~of:0000000000000226/8",
+        "epA": "of:0000000000000206/2",
+        "epB": "of:0000000000000226/8",
+        "type": "UiDeviceLink",
+        "portA": "2",
+        "portB": "8",
+        "rollup": [
+          {
+            "id": "of:0000000000000206/2~of:0000000000000226/8",
+            "epA": "of:0000000000000206/2",
+            "epB": "of:0000000000000226/8",
+            "type": "UiDeviceLink",
+            "portA": "2",
+            "portB": "8"
+          }
+        ]
+      },
+      {
+        "id": "00:BB:00:00:00:05/None~of:0000000000000203/7",
+        "epA": "00:BB:00:00:00:05/None",
+        "epB": "of:0000000000000203",
+        "type": "UiEdgeLink",
+        "portB": "7",
+        "rollup": [
+          {
+            "id": "00:BB:00:00:00:05/None~of:0000000000000203/7",
+            "epA": "00:BB:00:00:00:05/None",
+            "epB": "of:0000000000000203",
+            "type": "UiEdgeLink",
+            "portB": "7"
+          }
+        ]
+      },
+      {
+        "id": "00:DD:00:00:00:01/None~of:0000000000000207/3",
+        "epA": "00:DD:00:00:00:01/None",
+        "epB": "of:0000000000000207",
+        "type": "UiEdgeLink",
+        "portB": "3",
+        "rollup": [
+          {
+            "id": "00:DD:00:00:00:01/None~of:0000000000000207/3",
+            "epA": "00:DD:00:00:00:01/None",
+            "epB": "of:0000000000000207",
+            "type": "UiEdgeLink",
+            "portB": "3"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000203/1~of:0000000000000226/1",
+        "epA": "of:0000000000000203/1",
+        "epB": "of:0000000000000226/1",
+        "type": "UiDeviceLink",
+        "portA": "1",
+        "portB": "1",
+        "rollup": [
+          {
+            "id": "of:0000000000000203/1~of:0000000000000226/1",
+            "epA": "of:0000000000000203/1",
+            "epB": "of:0000000000000226/1",
+            "type": "UiDeviceLink",
+            "portA": "1",
+            "portB": "1"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000207/2~of:0000000000000247/1",
+        "epA": "of:0000000000000207/2",
+        "epB": "of:0000000000000247/1",
+        "type": "UiDeviceLink",
+        "portA": "2",
+        "portB": "1",
+        "rollup": [
+          {
+            "id": "of:0000000000000207/2~of:0000000000000247/1",
+            "epA": "of:0000000000000207/2",
+            "epB": "of:0000000000000247/1",
+            "type": "UiDeviceLink",
+            "portA": "2",
+            "portB": "1"
+          }
+        ]
+      },
+      {
+        "id": "00:99:66:00:00:01/None~of:0000000000000205/10",
+        "epA": "00:99:66:00:00:01/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "10",
+        "rollup": [
+          {
+            "id": "00:99:66:00:00:01/None~of:0000000000000205/10",
+            "epA": "00:99:66:00:00:01/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "10"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000208/1~of:0000000000000246/2",
+        "epA": "of:0000000000000208/1",
+        "epB": "of:0000000000000246/2",
+        "type": "UiDeviceLink",
+        "portA": "1",
+        "portB": "2",
+        "rollup": [
+          {
+            "id": "of:0000000000000208/1~of:0000000000000246/2",
+            "epA": "of:0000000000000208/1",
+            "epB": "of:0000000000000246/2",
+            "type": "UiDeviceLink",
+            "portA": "1",
+            "portB": "2"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000206/1~of:0000000000000226/7",
+        "epA": "of:0000000000000206/1",
+        "epB": "of:0000000000000226/7",
+        "type": "UiDeviceLink",
+        "portA": "1",
+        "portB": "7",
+        "rollup": [
+          {
+            "id": "of:0000000000000206/1~of:0000000000000226/7",
+            "epA": "of:0000000000000206/1",
+            "epB": "of:0000000000000226/7",
+            "type": "UiDeviceLink",
+            "portA": "1",
+            "portB": "7"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000226/9~of:0000000000000246/3",
+        "epA": "of:0000000000000226/9",
+        "epB": "of:0000000000000246/3",
+        "type": "UiDeviceLink",
+        "portA": "9",
+        "portB": "3",
+        "rollup": [
+          {
+            "id": "of:0000000000000226/9~of:0000000000000246/3",
+            "epA": "of:0000000000000226/9",
+            "epB": "of:0000000000000246/3",
+            "type": "UiDeviceLink",
+            "portA": "9",
+            "portB": "3"
+          }
+        ]
+      },
+      {
+        "id": "00:AA:00:00:00:04/None~of:0000000000000205/7",
+        "epA": "00:AA:00:00:00:04/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "7",
+        "rollup": [
+          {
+            "id": "00:AA:00:00:00:04/None~of:0000000000000205/7",
+            "epA": "00:AA:00:00:00:04/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "7"
+          }
+        ]
+      },
+      {
+        "id": "00:88:00:00:00:03/110~of:0000000000000205/11",
+        "epA": "00:88:00:00:00:03/110",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "11",
+        "rollup": [
+          {
+            "id": "00:88:00:00:00:03/110~of:0000000000000205/11",
+            "epA": "00:88:00:00:00:03/110",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "11"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000204/1~of:0000000000000226/3",
+        "epA": "of:0000000000000204/1",
+        "epB": "of:0000000000000226/3",
+        "type": "UiDeviceLink",
+        "portA": "1",
+        "portB": "3",
+        "rollup": [
+          {
+            "id": "of:0000000000000204/1~of:0000000000000226/3",
+            "epA": "of:0000000000000204/1",
+            "epB": "of:0000000000000226/3",
+            "type": "UiDeviceLink",
+            "portA": "1",
+            "portB": "3"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000203/2~of:0000000000000226/2",
+        "epA": "of:0000000000000203/2",
+        "epB": "of:0000000000000226/2",
+        "type": "UiDeviceLink",
+        "portA": "2",
+        "portB": "2",
+        "rollup": [
+          {
+            "id": "of:0000000000000203/2~of:0000000000000226/2",
+            "epA": "of:0000000000000203/2",
+            "epB": "of:0000000000000226/2",
+            "type": "UiDeviceLink",
+            "portA": "2",
+            "portB": "2"
+          }
+        ]
+      },
+      {
+        "id": "00:88:00:00:00:01/None~of:0000000000000205/12",
+        "epA": "00:88:00:00:00:01/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "12",
+        "rollup": [
+          {
+            "id": "00:88:00:00:00:01/None~of:0000000000000205/12",
+            "epA": "00:88:00:00:00:01/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "12"
+          }
+        ]
+      },
+      {
+        "id": "00:88:00:00:00:04/160~of:0000000000000206/6",
+        "epA": "00:88:00:00:00:04/160",
+        "epB": "of:0000000000000206",
+        "type": "UiEdgeLink",
+        "portB": "6",
+        "rollup": [
+          {
+            "id": "00:88:00:00:00:04/160~of:0000000000000206/6",
+            "epA": "00:88:00:00:00:04/160",
+            "epB": "of:0000000000000206",
+            "type": "UiEdgeLink",
+            "portB": "6"
+          }
+        ]
+      },
+      {
+        "id": "00:DD:00:00:00:02/None~of:0000000000000208/3",
+        "epA": "00:DD:00:00:00:02/None",
+        "epB": "of:0000000000000208",
+        "type": "UiEdgeLink",
+        "portB": "3",
+        "rollup": [
+          {
+            "id": "00:DD:00:00:00:02/None~of:0000000000000208/3",
+            "epA": "00:DD:00:00:00:02/None",
+            "epB": "of:0000000000000208",
+            "type": "UiEdgeLink",
+            "portB": "3"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000203/3~of:0000000000000227/1",
+        "epA": "of:0000000000000203/3",
+        "epB": "of:0000000000000227/1",
+        "type": "UiDeviceLink",
+        "portA": "3",
+        "portB": "1",
+        "rollup": [
+          {
+            "id": "of:0000000000000203/3~of:0000000000000227/1",
+            "epA": "of:0000000000000203/3",
+            "epB": "of:0000000000000227/1",
+            "type": "UiDeviceLink",
+            "portA": "3",
+            "portB": "1"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000208/2~of:0000000000000247/2",
+        "epA": "of:0000000000000208/2",
+        "epB": "of:0000000000000247/2",
+        "type": "UiDeviceLink",
+        "portA": "2",
+        "portB": "2",
+        "rollup": [
+          {
+            "id": "of:0000000000000208/2~of:0000000000000247/2",
+            "epA": "of:0000000000000208/2",
+            "epB": "of:0000000000000247/2",
+            "type": "UiDeviceLink",
+            "portA": "2",
+            "portB": "2"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000205/1~of:0000000000000226/5",
+        "epA": "of:0000000000000205/1",
+        "epB": "of:0000000000000226/5",
+        "type": "UiDeviceLink",
+        "portA": "1",
+        "portB": "5",
+        "rollup": [
+          {
+            "id": "of:0000000000000205/1~of:0000000000000226/5",
+            "epA": "of:0000000000000205/1",
+            "epB": "of:0000000000000226/5",
+            "type": "UiDeviceLink",
+            "portA": "1",
+            "portB": "5"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000204/2~of:0000000000000226/4",
+        "epA": "of:0000000000000204/2",
+        "epB": "of:0000000000000226/4",
+        "type": "UiDeviceLink",
+        "portA": "2",
+        "portB": "4",
+        "rollup": [
+          {
+            "id": "of:0000000000000204/2~of:0000000000000226/4",
+            "epA": "of:0000000000000204/2",
+            "epB": "of:0000000000000226/4",
+            "type": "UiDeviceLink",
+            "portA": "2",
+            "portB": "4"
+          }
+        ]
+      },
+      {
+        "id": "00:AA:00:00:00:01/None~of:0000000000000204/6",
+        "epA": "00:AA:00:00:00:01/None",
+        "epB": "of:0000000000000204",
+        "type": "UiEdgeLink",
+        "portB": "6",
+        "rollup": [
+          {
+            "id": "00:AA:00:00:00:01/None~of:0000000000000204/6",
+            "epA": "00:AA:00:00:00:01/None",
+            "epB": "of:0000000000000204",
+            "type": "UiEdgeLink",
+            "portB": "6"
+          }
+        ]
+      },
+      {
+        "id": "00:BB:00:00:00:03/None~of:0000000000000205/8",
+        "epA": "00:BB:00:00:00:03/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "8",
+        "rollup": [
+          {
+            "id": "00:BB:00:00:00:03/None~of:0000000000000205/8",
+            "epA": "00:BB:00:00:00:03/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "8"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000206/4~of:0000000000000227/8",
+        "epA": "of:0000000000000206/4",
+        "epB": "of:0000000000000227/8",
+        "type": "UiDeviceLink",
+        "portA": "4",
+        "portB": "8",
+        "rollup": [
+          {
+            "id": "of:0000000000000206/4~of:0000000000000227/8",
+            "epA": "of:0000000000000206/4",
+            "epB": "of:0000000000000227/8",
+            "type": "UiDeviceLink",
+            "portA": "4",
+            "portB": "8"
+          }
+        ]
+      },
+      {
+        "id": "00:AA:00:00:00:05/None~of:0000000000000203/6",
+        "epA": "00:AA:00:00:00:05/None",
+        "epB": "of:0000000000000203",
+        "type": "UiEdgeLink",
+        "portB": "6",
+        "rollup": [
+          {
+            "id": "00:AA:00:00:00:05/None~of:0000000000000203/6",
+            "epA": "00:AA:00:00:00:05/None",
+            "epB": "of:0000000000000203",
+            "type": "UiEdgeLink",
+            "portB": "6"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000205/5~of:0000000000000206/5",
+        "epA": "of:0000000000000205/5",
+        "epB": "of:0000000000000206/5",
+        "type": "UiDeviceLink",
+        "portA": "5",
+        "portB": "5",
+        "rollup": [
+          {
+            "id": "of:0000000000000205/5~of:0000000000000206/5",
+            "epA": "of:0000000000000205/5",
+            "epB": "of:0000000000000206/5",
+            "type": "UiDeviceLink",
+            "portA": "5",
+            "portB": "5"
+          }
+        ]
+      },
+      {
+        "id": "00:BB:00:00:00:02/None~of:0000000000000204/9",
+        "epA": "00:BB:00:00:00:02/None",
+        "epB": "of:0000000000000204",
+        "type": "UiEdgeLink",
+        "portB": "9",
+        "rollup": [
+          {
+            "id": "00:BB:00:00:00:02/None~of:0000000000000204/9",
+            "epA": "00:BB:00:00:00:02/None",
+            "epB": "of:0000000000000204",
+            "type": "UiEdgeLink",
+            "portB": "9"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000204/3~of:0000000000000227/3",
+        "epA": "of:0000000000000204/3",
+        "epB": "of:0000000000000227/3",
+        "type": "UiDeviceLink",
+        "portA": "3",
+        "portB": "3",
+        "rollup": [
+          {
+            "id": "of:0000000000000204/3~of:0000000000000227/3",
+            "epA": "of:0000000000000204/3",
+            "epB": "of:0000000000000227/3",
+            "type": "UiDeviceLink",
+            "portA": "3",
+            "portB": "3"
+          }
+        ]
+      },
+      {
+        "id": "00:EE:00:00:00:01/None~of:0000000000000207/4",
+        "epA": "00:EE:00:00:00:01/None",
+        "epB": "of:0000000000000207",
+        "type": "UiEdgeLink",
+        "portB": "4",
+        "rollup": [
+          {
+            "id": "00:EE:00:00:00:01/None~of:0000000000000207/4",
+            "epA": "00:EE:00:00:00:01/None",
+            "epB": "of:0000000000000207",
+            "type": "UiEdgeLink",
+            "portB": "4"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000203/4~of:0000000000000227/2",
+        "epA": "of:0000000000000203/4",
+        "epB": "of:0000000000000227/2",
+        "type": "UiDeviceLink",
+        "portA": "4",
+        "portB": "2",
+        "rollup": [
+          {
+            "id": "of:0000000000000203/4~of:0000000000000227/2",
+            "epA": "of:0000000000000203/4",
+            "epB": "of:0000000000000227/2",
+            "type": "UiDeviceLink",
+            "portA": "4",
+            "portB": "2"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000205/2~of:0000000000000226/6",
+        "epA": "of:0000000000000205/2",
+        "epB": "of:0000000000000226/6",
+        "type": "UiDeviceLink",
+        "portA": "2",
+        "portB": "6",
+        "rollup": [
+          {
+            "id": "of:0000000000000205/2~of:0000000000000226/6",
+            "epA": "of:0000000000000205/2",
+            "epB": "of:0000000000000226/6",
+            "type": "UiDeviceLink",
+            "portA": "2",
+            "portB": "6"
+          }
+        ]
+      },
+      {
+        "id": "00:99:00:00:00:01/None~of:0000000000000205/10",
+        "epA": "00:99:00:00:00:01/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "10",
+        "rollup": [
+          {
+            "id": "00:99:00:00:00:01/None~of:0000000000000205/10",
+            "epA": "00:99:00:00:00:01/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "10"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000205/4~of:0000000000000227/6",
+        "epA": "of:0000000000000205/4",
+        "epB": "of:0000000000000227/6",
+        "type": "UiDeviceLink",
+        "portA": "4",
+        "portB": "6",
+        "rollup": [
+          {
+            "id": "of:0000000000000205/4~of:0000000000000227/6",
+            "epA": "of:0000000000000205/4",
+            "epB": "of:0000000000000227/6",
+            "type": "UiDeviceLink",
+            "portA": "4",
+            "portB": "6"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000206/3~of:0000000000000227/7",
+        "epA": "of:0000000000000206/3",
+        "epB": "of:0000000000000227/7",
+        "type": "UiDeviceLink",
+        "portA": "3",
+        "portB": "7",
+        "rollup": [
+          {
+            "id": "of:0000000000000206/3~of:0000000000000227/7",
+            "epA": "of:0000000000000206/3",
+            "epB": "of:0000000000000227/7",
+            "type": "UiDeviceLink",
+            "portA": "3",
+            "portB": "7"
+          }
+        ]
+      },
+      {
+        "id": "00:BB:00:00:00:04/None~of:0000000000000205/9",
+        "epA": "00:BB:00:00:00:04/None",
+        "epB": "of:0000000000000205",
+        "type": "UiEdgeLink",
+        "portB": "9",
+        "rollup": [
+          {
+            "id": "00:BB:00:00:00:04/None~of:0000000000000205/9",
+            "epA": "00:BB:00:00:00:04/None",
+            "epB": "of:0000000000000205",
+            "type": "UiEdgeLink",
+            "portB": "9"
+          }
+        ]
+      },
+      {
+        "id": "00:AA:00:00:00:02/None~of:0000000000000204/7",
+        "epA": "00:AA:00:00:00:02/None",
+        "epB": "of:0000000000000204",
+        "type": "UiEdgeLink",
+        "portB": "7",
+        "rollup": [
+          {
+            "id": "00:AA:00:00:00:02/None~of:0000000000000204/7",
+            "epA": "00:AA:00:00:00:02/None",
+            "epB": "of:0000000000000204",
+            "type": "UiEdgeLink",
+            "portB": "7"
+          }
+        ]
+      },
+      {
+        "id": "00:BB:00:00:00:01/None~of:0000000000000204/8",
+        "epA": "00:BB:00:00:00:01/None",
+        "epB": "of:0000000000000204",
+        "type": "UiEdgeLink",
+        "portB": "8",
+        "rollup": [
+          {
+            "id": "00:BB:00:00:00:01/None~of:0000000000000204/8",
+            "epA": "00:BB:00:00:00:01/None",
+            "epB": "of:0000000000000204",
+            "type": "UiEdgeLink",
+            "portB": "8"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000207/1~of:0000000000000246/1",
+        "epA": "of:0000000000000207/1",
+        "epB": "of:0000000000000246/1",
+        "type": "UiDeviceLink",
+        "portA": "1",
+        "portB": "1",
+        "rollup": [
+          {
+            "id": "of:0000000000000207/1~of:0000000000000246/1",
+            "epA": "of:0000000000000207/1",
+            "epB": "of:0000000000000246/1",
+            "type": "UiDeviceLink",
+            "portA": "1",
+            "portB": "1"
+          }
+        ]
+      },
+      {
+        "id": "00:88:00:00:00:02/None~of:0000000000000206/7",
+        "epA": "00:88:00:00:00:02/None",
+        "epB": "of:0000000000000206",
+        "type": "UiEdgeLink",
+        "portB": "7",
+        "rollup": [
+          {
+            "id": "00:88:00:00:00:02/None~of:0000000000000206/7",
+            "epA": "00:88:00:00:00:02/None",
+            "epB": "of:0000000000000206",
+            "type": "UiEdgeLink",
+            "portB": "7"
+          }
+        ]
+      },
+      {
+        "id": "00:EE:00:00:00:02/None~of:0000000000000208/4",
+        "epA": "00:EE:00:00:00:02/None",
+        "epB": "of:0000000000000208",
+        "type": "UiEdgeLink",
+        "portB": "4",
+        "rollup": [
+          {
+            "id": "00:EE:00:00:00:02/None~of:0000000000000208/4",
+            "epA": "00:EE:00:00:00:02/None",
+            "epB": "of:0000000000000208",
+            "type": "UiEdgeLink",
+            "portB": "4"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000204/4~of:0000000000000227/4",
+        "epA": "of:0000000000000204/4",
+        "epB": "of:0000000000000227/4",
+        "type": "UiDeviceLink",
+        "portA": "4",
+        "portB": "4",
+        "rollup": [
+          {
+            "id": "of:0000000000000204/4~of:0000000000000227/4",
+            "epA": "of:0000000000000204/4",
+            "epB": "of:0000000000000227/4",
+            "type": "UiDeviceLink",
+            "portA": "4",
+            "portB": "4"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000203/5~of:0000000000000204/5",
+        "epA": "of:0000000000000203/5",
+        "epB": "of:0000000000000204/5",
+        "type": "UiDeviceLink",
+        "portA": "5",
+        "portB": "5",
+        "rollup": [
+          {
+            "id": "of:0000000000000203/5~of:0000000000000204/5",
+            "epA": "of:0000000000000203/5",
+            "epB": "of:0000000000000204/5",
+            "type": "UiDeviceLink",
+            "portA": "5",
+            "portB": "5"
+          }
+        ]
+      },
+      {
+        "id": "of:0000000000000227/9~of:0000000000000247/3",
+        "epA": "of:0000000000000227/9",
+        "epB": "of:0000000000000247/3",
+        "type": "UiDeviceLink",
+        "portA": "9",
+        "portB": "3",
+        "rollup": [
+          {
+            "id": "of:0000000000000227/9~of:0000000000000247/3",
+            "epA": "of:0000000000000227/9",
+            "epB": "of:0000000000000247/3",
+            "type": "UiDeviceLink",
+            "portA": "9",
+            "portB": "3"
+          }
+        ]
+      }
+    ],
+    "devices": [
+      [],
+      [],
+      [
+        {
+          "id": "of:0000000000000246",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "40.15",
+            "name": "s246",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59980",
+            "longitude": "-121.679"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 40.15,
+            "longOrX": -121.679
+          }
+        },
+        {
+          "id": "of:0000000000000206",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "36.766",
+            "name": "s206",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59975",
+            "longitude": "-92.029"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 36.766,
+            "longOrX": -92.029
+          }
+        },
+        {
+          "id": "of:0000000000000227",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "44.205",
+            "name": "s227",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59979",
+            "longitude": "-96.359"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 44.205,
+            "longOrX": -96.359
+          }
+        },
+        {
+          "id": "of:0000000000000208",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "36.766",
+            "name": "s208",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59977",
+            "longitude": "-116.029"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 36.766,
+            "longOrX": -116.029
+          }
+        },
+        {
+          "id": "of:0000000000000205",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "36.766",
+            "name": "s205",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59974",
+            "longitude": "-96.89"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 36.766,
+            "longOrX": -96.89
+          }
+        },
+        {
+          "id": "of:0000000000000247",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "40.205",
+            "name": "s247",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59981",
+            "longitude": "-117.359"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 40.205,
+            "longOrX": -117.359
+          }
+        },
+        {
+          "id": "of:0000000000000226",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "44.15",
+            "name": "s226",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59978",
+            "longitude": "-107.679"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 44.15,
+            "longOrX": -107.679
+          }
+        },
+        {
+          "id": "of:0000000000000203",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "36.766",
+            "name": "s203",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59972",
+            "longitude": "-111.359"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 36.766,
+            "longOrX": -111.359
+          }
+        },
+        {
+          "id": "of:0000000000000204",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "36.766",
+            "name": "s204",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59973",
+            "longitude": "-106.359"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 36.766,
+            "longOrX": -106.359
+          }
+        },
+        {
+          "id": "of:0000000000000207",
+          "nodeType": "device",
+          "type": "switch",
+          "online": true,
+          "master": "10.192.19.68",
+          "layer": "def",
+          "props": {
+            "managementAddress": "10.192.19.69",
+            "protocol": "OF_13",
+            "driver": "ofdpa-ovs",
+            "latitude": "36.766",
+            "name": "s207",
+            "locType": "geo",
+            "channelId": "10.192.19.69:59976",
+            "longitude": "-122.359"
+          },
+          "location": {
+            "locType": "geo",
+            "latOrY": 36.766,
+            "longOrX": -122.359
+          }
+        }
+      ]
+    ],
+    "hosts": [
+      [],
+      [],
+      [
+        {
+          "id": "00:88:00:00:00:03/110",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::288:ff:fe00:3",
+            "2000::102",
+            "10.0.1.2"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:DD:00:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:88:00:00:00:04/160",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::288:ff:fe00:4",
+            "10.0.6.2",
+            "2000::602"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:BB:00:00:00:02/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2bb:ff:fe00:2"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:AA:00:00:00:05/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:88:00:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::288:ff:fe00:1",
+            "2000::101",
+            "10.0.1.1"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:AA:00:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:AA:00:00:00:03/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:BB:00:00:00:04/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2bb:ff:fe00:4"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:EE:00:00:00:02/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2ee:ff:fe00:2"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:99:00:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "10.0.3.253",
+            "fe80::299:ff:fe00:1"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:99:66:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::299:66ff:fe00:1",
+            "2000::3fd"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:EE:00:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2ee:ff:fe00:1"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:BB:00:00:00:01/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2bb:ff:fe00:1"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:BB:00:00:00:03/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2bb:ff:fe00:3"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:AA:00:00:00:04/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:BB:00:00:00:05/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::2bb:ff:fe00:5"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:88:00:00:00:02/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [
+            "fe80::288:ff:fe00:2",
+            "2000::601",
+            "10.0.6.1"
+          ],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:AA:00:00:00:02/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        },
+        {
+          "id": "00:DD:00:00:00:02/None",
+          "nodeType": "host",
+          "layer": "def",
+          "ips": [],
+          "props": {},
+          "configured": false
+        }
+      ]
+    ],
+    "layerOrder": [
+      "opt",
+      "pkt",
+      "def"
+    ]
+  }
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts
index 0492858..bc3fd76 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/topology/topology.component.spec.ts
@@ -120,6 +120,7 @@
 
 class MockTrafficService {
     init(force: ForceSvgComponent) {}
+    destroy() {}
 }
 
 class MockLayoutService {}