GUI2 Extract Topology view in to its own library

Change-Id: I45597d0902c99b5b3d606966866cc518011c54a0
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.css
new file mode 100644
index 0000000..05402c8
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.css
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019-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.
+ */
+/* --- Topo Details Panel --- */
+
+#topo2-p-detail {
+    padding: 16px;
+    opacity: 1;
+    right: 20px;
+    width: 260px;
+    top: 390px;
+}
+
+#topo2-p-detail  div.actionBtns {
+    padding-top: 6px;
+}
+
+html[data-platform='iPad'] {
+    top: 386px;
+}
+
+.actionBtns .actionBtn {
+    display: inline-block;
+}
+.actionBtns .actionBtn svg {
+    width: 28px;
+    height: 28px;
+}
+
+div.actionBtns {
+    padding-top: 6px;
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.html b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.html
new file mode 100644
index 0000000..ca23ca1
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.html
@@ -0,0 +1,66 @@
+<!--
+~ Copyright 2019-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.
+-->
+<div id="topo2-p-detail" class="floatpanel topo2-p"
+     [@detailsPanelState]="on && selectedNode !== undefined">
+    <!-- Template explanation - Create a HTML header which has an SVG icon along
+    side title text. -->
+    <div class="header">
+        <div class="icon clickable">
+            <onos-icon
+                    [iconSize]="26"
+                    [iconId]="showDetails?.glyphId">
+            </onos-icon>
+        </div>
+        <h2 class="clickable">{{ showDetails?.title }}</h2>
+    </div>
+    <div class="body">
+        <table>
+            <tbody>
+            <!-- Template explanation - Inside a HTML table, create a row per
+            item in the propOrder array returned through the WSS showDetails.
+            If the row name contains only '-' then draw a horiz rule otherwise
+            create a cell for the name and another for the value -->
+                <tr *ngFor="let p of showDetails?.propOrder">
+                    <td *ngIf="showDetails?.propLabels[p] !== '-'"
+                        class="label">{{showDetails?.propLabels[p]}} :</td>
+                    <td *ngIf="showDetails?.propLabels[p] !== '-'"
+                        class="value">{{ showDetails?.propValues[p]}}</td>
+                    <!-- If the label is '-' then insert a horiz line -->
+                    <td *ngIf="showDetails?.propLabels[p] === '-'"
+                        colspan="2"><hr></td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+    <div class="footer">
+        <hr>
+        <div class="actionBtns">
+            <!-- Template explanation - Inside the panel footer, create an SVG icon
+            per entry in the buttons array returned from the WSS showDetails
+            The icons used here are loaded in the ForceSvgComponent
+            -->
+            <div *ngFor="let btn of showDetails?.buttons" class="actionBtn">
+                <onos-icon id="topo2-p-detail-core-{{ btn }}"
+                           (click)="navto(buttonAttribs(btn).path, showDetails.navPath, showDetails.id)"
+                        [iconSize]="25"
+                        [iconId]="buttonAttribs(btn).gid"
+                        [toolTip]="lionFn(buttonAttribs(btn).tt)"
+                        classes="button icon selected">
+                </onos-icon>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.spec.ts
new file mode 100644
index 0000000..620d931
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.spec.ts
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019-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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { of } from 'rxjs';
+import { DetailsComponent } from './details.component';
+
+import {
+    FnService, LionService,
+    LogService, IconComponent
+} from 'gui2-fw-lib';
+import {RouterTestingModule} from '@angular/router/testing';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+/**
+ * ONOS GUI -- Topology View Details Panel-- Unit Tests
+ */
+describe('DetailsComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: DetailsComponent;
+    let fixture: ComponentFixture<DetailsComponent>;
+
+    const bundleObj = {
+        'core.view.Flow': {
+            test: 'test1'
+        }
+    };
+    const mockLion = (key) => {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule, RouterTestingModule ],
+            declarations: [ DetailsComponent, IconComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                {
+                    provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(DetailsComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts
new file mode 100644
index 0000000..ea1462c
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.component.ts
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2019-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 {
+    Component,
+    Input,
+    OnChanges,
+    OnDestroy,
+    OnInit,
+    SimpleChanges
+} from '@angular/core';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+import {
+    DetailsPanelBaseImpl,
+    FnService, LionService,
+    LoadingService,
+    LogService,
+    WebSocketService
+} from 'gui2-fw-lib';
+import {Host, Link, LinkType, UiElement} from '../../layer/forcesvg/models';
+import {Params, Router} from '@angular/router';
+
+
+interface ButtonAttrs {
+    gid: string;
+    tt: string;
+    path: string;
+}
+
+const SHOWDEVICEVIEW: ButtonAttrs = {
+    gid: 'deviceTable',
+    tt: 'tt_ctl_show_device',
+    path: 'device',
+};
+const SHOWFLOWVIEW: ButtonAttrs = {
+    gid: 'flowTable',
+    tt: 'title_flows',
+    path: 'flow',
+};
+const SHOWPORTVIEW: ButtonAttrs = {
+    gid: 'portTable',
+    tt: 'tt_ctl_show_port',
+    path: 'port',
+};
+const SHOWGROUPVIEW: ButtonAttrs = {
+    gid: 'groupTable',
+    tt: 'tt_ctl_show_group',
+    path: 'group',
+};
+const SHOWMETERVIEW: ButtonAttrs = {
+    gid: 'meterTable',
+    tt: 'tt_ctl_show_meter',
+    path: 'meter',
+};
+const SHOWPIPECONFVIEW: ButtonAttrs = {
+    gid: 'pipeconfTable',
+    tt: 'tt_ctl_show_pipeconf',
+    path: 'pipeconf',
+};
+
+interface ShowDetails {
+    buttons: string[];
+    glyphId: string;
+    id: string;
+    navPath: string;
+    propLabels: Object;
+    propOrder: string[];
+    propValues: Object;
+    title: string;
+}
+/**
+ * ONOS GUI -- Topology Details Panel.
+ * Displays details of selected device. When no device is selected the panel slides
+ * off to the side and disappears
+ */
+@Component({
+    selector: 'onos-details',
+    templateUrl: './details.component.html',
+    styleUrls: [
+        './details.component.css', './details.theme.css',
+        '../../topology.common.css',
+        '../../../../fw/widget/panel.css', '../../../../fw/widget/panel-theme.css'
+    ],
+    animations: [
+        trigger('detailsPanelState', [
+            state('true', style({
+                transform: 'translateX(0%)',
+                opacity: '1.0'
+            })),
+            state('false', style({
+                transform: 'translateX(100%)',
+                opacity: '0'
+            })),
+            transition('0 => 1', animate('100ms ease-in')),
+            transition('1 => 0', animate('100ms ease-out'))
+        ])
+    ]
+})
+export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
+    @Input() selectedNode: UiElement = undefined; // Populated when user selects node or link
+    @Input() on: boolean = false; // Override the parent class attribute
+
+    // deferred localization strings
+    lionFn; // Function
+    showDetails: ShowDetails; // Will be populated on callback. Cleared if nothing is selected
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected ls: LoadingService,
+        protected router: Router,
+        protected wss: WebSocketService,
+        private lion: LionService
+    ) {
+        super(fs, ls, log, wss, 'topo');
+
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('flow', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+
+        this.log.debug('Topo DetailsComponent constructed');
+    }
+
+    /**
+     * When the component is initializing set up the handler for callbacks of
+     * ShowDetails from the WSS. Set the variable showDetails when ever a callback
+     * is made
+     */
+    ngOnInit(): void {
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            ['showDetails', (data) => {
+                    this.showDetails = data;
+                    // this.log.debug('showDetails received', data);
+                }
+            ]
+        ]));
+        this.log.debug('Topo DetailsComponent initialized');
+    }
+
+    /**
+     * When the component is being unloaded then unbind the WSS handler.
+     */
+    ngOnDestroy(): void {
+        this.wss.unbindHandlers(['showDetails']);
+        this.log.debug('Topo DetailsComponent destroyed');
+    }
+
+    /**
+     * If changes are detected on the Input param selectedNode, call on WSS sendEvent
+     * Note the difference in call to the WSS with requestDetails between a node
+     * and a link - the handling is done in TopologyViewMessageHandler#RequestDetails.process()
+     *
+     * The WSS will call back asynchronously (see fn in ngOnInit())
+     *
+     * @param changes Simple Changes set of updates
+     */
+    ngOnChanges(changes: SimpleChanges): void {
+        if (changes['selectedNode']) {
+            this.selectedNode = changes['selectedNode'].currentValue;
+            let type: any;
+
+            if (this.selectedNode === undefined) {
+                // Selection has been cleared
+                this.showDetails = <ShowDetails>{};
+                return;
+            }
+
+            if (this.selectedNode.hasOwnProperty('nodeType')) { // For Device, Host, SubRegion
+                type = (<Host>this.selectedNode).nodeType;
+                this.wss.sendEvent('requestDetails', {
+                    id: this.selectedNode.id,
+                    class: type,
+                });
+            } else if (this.selectedNode.hasOwnProperty('type')) { // Must be link
+                const link: Link = <Link>this.selectedNode;
+                if (<LinkType><unknown>LinkType[link.type] === LinkType.UiEdgeLink) { // Number based enum
+                    this.wss.sendEvent('requestDetails', {
+                        key: link.id,
+                        class: 'link',
+                        sourceId: link.epA,
+                        targetId: Link.deviceNameFromEp(link.epB),
+                        targetPort: link.portB,
+                        isEdgeLink: true
+                    });
+                } else {
+                    this.wss.sendEvent('requestDetails', {
+                        key: link.id,
+                        class: 'link',
+                        sourceId: Link.deviceNameFromEp(link.epA),
+                        sourcePort: link.portA,
+                        targetId: Link.deviceNameFromEp(link.epB),
+                        targetPort: link.portB,
+                        isEdgeLink: false
+                    });
+                }
+            } else {
+                this.log.warn('Unexpected type for selected element', this.selectedNode);
+            }
+        }
+    }
+
+    /**
+     * Table of core button attributes to return per button icon
+     * @param btnName The name of the button
+     * @returns A structure with the button attributes
+     */
+    buttonAttribs(btnName: string): ButtonAttrs {
+        switch (btnName) {
+            case 'showDeviceView':
+                return SHOWDEVICEVIEW;
+            case 'showFlowView':
+                return SHOWFLOWVIEW;
+            case 'showPortView':
+                return SHOWPORTVIEW;
+            case 'showGroupView':
+                return SHOWGROUPVIEW;
+            case 'showMeterView':
+                return SHOWMETERVIEW;
+            case 'showPipeConfView':
+                return SHOWPIPECONFVIEW;
+            default:
+                return <ButtonAttrs>{
+                    gid: btnName,
+                    path: btnName
+                };
+        }
+    }
+
+    /**
+     * Navigate using Angular Routing. Combines the parameters to generate a relative URL
+     * e.g. if params are 'meter', 'device' and 'null:0000000000001' then the
+     * navigation URL will become "http://localhost:4200/#/meter?devId=null:0000000000000002"
+     *
+     * @param path The path to navigate to
+     * @param navPath The parameter name to use
+     * @param selId the parameter value to use
+     */
+    navto(path: string, navPath: string, selId: string): void {
+        this.log.debug('navigate to', path, 'for', navPath, '=', selId);
+        // Special case until it's fixed
+        if (selId) {
+            if (navPath === 'device') {
+                navPath = 'devId';
+            }
+            const queryPar: Params = {};
+            queryPar[navPath] = selId;
+            this.router.navigate([path], { queryParams: queryPar });
+        }
+    }
+
+    /**
+     * Read the LION bundle for Details panel and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.Flow');
+
+    }
+
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.theme.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.theme.css
new file mode 100644
index 0000000..7ad72dd
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/details/details.theme.css
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+/* --- Topo Details Panel Theme --- */
+
+#topo2-p-detail svg {
+    background: none;
+}
+
+#topo2-p-detail .header svg .glyph {
+    fill: #c0242b;
+}
+
+.dark #topo2-p-detail .header svg .glyph {
+    fill: #91292f;
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.css
new file mode 100644
index 0000000..f335726
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.css
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+/* --- Topo Instance Panel --- */
+
+#topo-p-instance div.onosInst {
+    display: inline-block;
+    width: 170px;
+    height: 85px;
+    cursor: pointer;
+}
+
+#topo-p-instance svg text.instTitle {
+    font-size: 11pt;
+    font-weight: bold;
+    font-variant: small-caps;
+    text-transform: uppercase;
+}
+#topo-p-instance svg text.instLabel {
+    font-size: 10pt;
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.html b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.html
new file mode 100644
index 0000000..f5a2859
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.html
@@ -0,0 +1,40 @@
+<!--
+~ 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.
+-->
+<div id="topo-p-instance" class="floatpanel" [ngStyle]="{'left': '20px', 'top':divTopPx+'px', 'width': (onosInstances.length * 170)+'px', 'height': '85px'}" [@instancePanelState]="on">
+    <div *ngFor="let inst of onosInstances | keyvalue ; let i=index"
+         [ngClass]="['onosInst', inst.value.online?'online':'', inst.value.ready? 'ready': '', mastership?'mastership':'', 'affinity']"
+            (click)="chooseMastership(inst.value.id)">
+        <svg xmlns="http://www.w3.org/2000/svg" width="170" height="85" viewBox="0 0 170 85">
+            <!-- The following blue-glow effect is applied (through CSS) when mastership style is activated on a rectangle -->
+            <filter x="-50%" y="-50%" width="200%" height="200%" id="blue-glow">
+                <feColorMatrix type="matrix" values="0 0 0 0  0 0 0 0 0  0 0 0 0 0  0.7 0 0 0 1  0 "></feColorMatrix>
+                <feGaussianBlur stdDeviation="3" result="coloredBlur"></feGaussianBlur>
+                <feMerge>
+                    <feMergeNode in="coloredBlur"></feMergeNode>
+                    <feMergeNode in="SourceGraphic"></feMergeNode>
+                </feMerge>
+            </filter>
+            <rect x="5" y="5" width="160" height="30" [ngStyle]="{ 'fill': panelColour(i)}"></rect>
+            <text class="instTitle" x="48" y="27">{{ inst.value.id }}</text>
+            <rect x="5" y="35" width="160" height="45"></rect>
+            <text class="instLabel ip" x="48" y="55">{{ inst.value.ip }}</text>
+            <use width="20" height="20" class="glyph badgeIcon bird" xlink:href="#bird" transform="translate(15,10)"></use>
+            <use *ngIf="inst.value.ready" width="16" height="16" class="glyph overlay badgeIcon readyBadge" xlink:href="#checkMark" transform="translate(18,40)"></use>
+            <text class="instLabel ns" x="48" y="73">{{lionFn('devices')}} {{ inst.value.switches }}</text>
+            <use *ngIf="inst.value.uiAttached" width="24" height="24" class="glyph overlay badgeIcon uiBadge" xlink:href="#uiAttached" transform="translate(14,54)"></use>
+        </svg>
+    </div>
+</div>
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.spec.ts
new file mode 100644
index 0000000..a03f23e
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.spec.ts
@@ -0,0 +1,84 @@
+/*
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+import { InstanceComponent } from './instance.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import {
+    FnService,
+    LogService
+} from 'gui2-fw-lib';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+/**
+ * ONOS GUI -- Topology View Instance Panel-- Unit Tests
+ */
+describe('InstanceComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: InstanceComponent;
+    let fixture: ComponentFixture<InstanceComponent>;
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ InstanceComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(InstanceComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.ts
new file mode 100644
index 0000000..487550d
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.component.ts
@@ -0,0 +1,133 @@
+/*
+ * 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 {
+    Component,
+    Input,
+    Output,
+    EventEmitter
+} from '@angular/core';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import {
+    LogService,
+    LoadingService,
+    FnService,
+    PanelBaseImpl,
+    IconService,
+    SvgUtilService, LionService
+} from 'gui2-fw-lib';
+
+/**
+ * A model of instance information that drives each panel
+ */
+export interface Instance {
+    id: string;
+    ip: string;
+    online: boolean;
+    ready: boolean;
+    switches: number;
+    uiAttached: boolean;
+}
+
+/**
+ * ONOS GUI -- Topology Instances Panel.
+ * Displays ONOS instances. The onosInstances Array gets updated by topology.service
+ * whenever a topo2AllInstances update arrives back on the WebSocket
+ *
+ * This emits a mastership event when the user clicks on an instance, to
+ * see the devices that it has mastership of.
+ */
+@Component({
+    selector: 'onos-instance',
+    templateUrl: './instance.component.html',
+    styleUrls: [
+        './instance.component.css', './instance.theme.css',
+        '../../topology.common.css',
+        '../../../../fw/widget/panel.css', '../../../../fw/widget/panel-theme.css'
+    ],
+    animations: [
+        trigger('instancePanelState', [
+            state('true', style({
+                transform: 'translateX(0%)',
+                opacity: '1.0'
+            })),
+            state('false', style({
+                transform: 'translateX(-100%)',
+                opacity: '0.0'
+            })),
+            transition('0 => 1', animate('100ms ease-in')),
+            transition('1 => 0', animate('100ms ease-out'))
+        ])
+    ]
+})
+export class InstanceComponent extends PanelBaseImpl {
+    @Input() divTopPx: number = 100;
+    @Input() on: boolean = false; // Override the parent class attribute
+    @Output() mastershipEvent = new EventEmitter<string>();
+    public onosInstances: Array<Instance>;
+    public mastership: string;
+    lionFn; // Function
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected ls: LoadingService,
+        protected is: IconService,
+        protected sus: SvgUtilService,
+        private lion: LionService
+    ) {
+        super(fs, ls, log);
+        this.onosInstances = <Array<Instance>>[];
+
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('topo-inst', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+        this.log.debug('InstanceComponent constructed');
+    }
+
+    /**
+     * Get a colour for the banner of the nth panel
+     * @param idx The index of the panel (0-6)
+     */
+    panelColour(idx: number): string {
+        return this.sus.cat7().getColor(idx, false, '');
+    }
+
+    /**
+     * Toggle the display of mastership
+     * If the same instance is clicked a second time then cancel display of mastership
+     * @param instId The instance to display mastership for
+     */
+    chooseMastership(instId: string): void {
+        if (this.mastership === instId) {
+            this.mastership = '';
+        } else {
+            this.mastership = instId;
+        }
+        this.mastershipEvent.emit(this.mastership);
+        this.log.debug('Instance', this.mastership, 'chosen on GUI');
+    }
+
+    /**
+     * Read the LION bundle for Details panel and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.Topo');
+
+    }
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.theme.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.theme.css
new file mode 100644
index 0000000..3be7bdd
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/instance/instance.theme.css
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+/* --- Topo Instance Panel --- */
+
+#topo-p-instance svg rect {
+    stroke-width: 0;
+    fill: #fbfbfb;
+}
+
+/* body of an instance */
+#topo-p-instance .online svg rect {
+    opacity: 1;
+    fill: #fbfbfb;
+}
+
+#topo-p-instance svg .glyph {
+    fill: #fff;
+}
+#topo-p-instance .online svg .glyph {
+    fill: #fff;
+}
+.dark #topo-p-instance .online svg .glyph.overlay {
+    fill: #fff;
+}
+
+/* offline */
+#topo-p-instance svg .badgeIcon {
+    opacity: 0.4;
+    fill: #939598;
+}
+
+/* online */
+#topo-p-instance .online svg .badgeIcon {
+    opacity: 1.0;
+    fill: #939598;
+}
+#topo-p-instance .online svg .badgeIcon.bird {
+    fill: #ffffff;
+}
+
+#topo-p-instance svg .readyBadge {
+    visibility: hidden;
+}
+#topo-p-instance .ready svg .readyBadge {
+    visibility: visible;
+}
+
+#topo-p-instance svg text {
+    text-anchor: start;
+    opacity: 0.5;
+    fill: #3c3a3a;
+}
+
+#topo-p-instance .online svg text {
+    opacity: 1.0;
+    fill: #3c3a3a;
+}
+
+#topo-p-instance .onosInst.mastership {
+    opacity: 0.3;
+}
+#topo-p-instance .onosInst.mastership.affinity {
+    opacity: 1.0;
+}
+#topo-p-instance .onosInst.mastership.affinity svg rect {
+    filter: url(#blue-glow);
+}
+
+.firefox #topo-p-instance .onosInst.mastership.affinity svg rect {
+    filter: url(#blue-glow);
+}
+
+.dark #topo-p-instance {
+    background-color: #2f313c;
+    color: #c2c2b7;
+    border: 1px solid #364144;
+
+}
+
+.dark #topo-p-instance svg rect {
+    stroke-width: 0;
+    fill: #525660;
+}
+
+/* body of an instance */
+.dark #topo-p-instance .online svg rect {
+    opacity: 1;
+    fill: #838992;
+}
+
+.dark #topo-p-instance svg .glyph {
+    fill: #ddd;
+}
+.dark #topo-p-instance .online svg .glyph {
+    fill: #fff;
+}
+.dark #topo-p-instance .online svg .glyph.overlay {
+    fill: #c7c7c7;
+}
+
+/* offline */
+.dark #topo-p-instance svg .badgeIcon {
+    opacity: 0.4;
+    fill: #939598;
+}
+
+/* online */
+.dark #topo-p-instance .online svg .badgeIcon {
+    opacity: 1.0;
+    fill: #939598;
+}
+.dark #topo-p-instance .online svg .badgeIcon.bird {
+    fill: #ffffff;
+}
+
+.dark #topo-p-instance svg text {
+    text-anchor: start;
+    opacity: 0.5;
+    fill: #aaa;
+}
+
+.dark #topo-p-instance .online svg text {
+    opacity: 1.0;
+    fill: #fff;
+}
+
+.dark #topo-p-instance .onosInst.mastership {
+    opacity: 0.3;
+}
+.dark #topo-p-instance .onosInst.mastership.affinity {
+    opacity: 1.0;
+}
+.dark #topo-p-instance .onosInst.mastership.affinity svg rect {
+    filter: url(#blue-glow);
+}
+
+.dark.firefox #topo-p-instance .onosInst.mastership.affinity svg rect {
+    filter: url(#blue-glow);
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.css
new file mode 100644
index 0000000..59c0d78
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.css
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019-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.
+ */
+
+/**
+ * ONOS GUI -- Topology Map Selector -- CSS file
+ */
+.dialog h2 {
+    margin: 0;
+    word-wrap: break-word;
+    display: inline-block;
+    width: 210px;
+    vertical-align: middle;
+}
+
+.dialog .dialog-button {
+    display: inline-block;
+    cursor: pointer;
+    height: 20px;
+    padding: 6px 8px 2px 8px;
+    margin: 4px;
+    float: right;
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.html b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.html
new file mode 100644
index 0000000..579a43b
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.html
@@ -0,0 +1,33 @@
+<!--
+~ Copyright 2019-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.
+-->
+<div id="topo-p-dialog"
+     class="floatpanel dialog topo-p"
+     style="opacity: 1; left: 20px; width: 300px;">
+    <div class="header">
+        <h2>{{ lionFn('title_select_map') }}</h2>
+    </div>
+    <div class="map-list">
+        <form [formGroup]="form">
+            <select formControlName="mapid">
+                <option *ngFor="let o of mapSelectorResponse.order" [ngValue]="o">{{ mapSelectorResponse.maps[o]['description'] }}</option>
+            </select>
+        </form>
+    </div>
+    <div class="footer">
+        <div class="dialog-button" (click)="choice(form.value)">{{ lionFn('ok') }}</div>
+        <div class="dialog-button" (click)="choice(undefined)">{{ lionFn('close') }}</div>
+    </div>
+</div>
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.spec.ts
new file mode 100644
index 0000000..4d95361
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.spec.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019-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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MapSelectorComponent } from './mapselector.component';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {FnService, LogService} from 'gui2-fw-lib';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+describe('MapSelectorComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: MapSelectorComponent;
+    let fixture: ComponentFixture<MapSelectorComponent>;
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+
+        TestBed.configureTestingModule({
+            imports: [
+                FormsModule,
+                ReactiveFormsModule
+            ],
+            declarations: [ MapSelectorComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(MapSelectorComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
+
+// Expecting WebSocket request and response similar to:
+//
+// {"event":"mapSelectorRequest","payload":{}}
+//
+// {
+//     "event": "mapSelectorResponse",
+//     "payload": {
+//     "order": ["australia", "americas", "n_america", "s_america", "usa", "bayareaGEO",
+//     "europe", "italy", "uk", "japan", "s_korea", "taiwan", "africa", "oceania", "asia"],
+//         "maps": {
+//         "australia": {
+//             "id": "australia",
+//             "description": "Australia",
+//             "filePath": "*australia",
+//             "scale": 1.0
+//         },
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.ts
new file mode 100644
index 0000000..c97e0ca
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.component.ts
@@ -0,0 +1,87 @@
+
+import {
+    Component, EventEmitter, OnChanges,
+    OnDestroy,
+    OnInit, Output, SimpleChanges,
+} from '@angular/core';
+import {
+    DetailsPanelBaseImpl,
+    FnService,
+    LionService, LoadingService,
+    LogService,
+    WebSocketService
+} from 'gui2-fw-lib';
+import {FormControl, FormGroup} from '@angular/forms';
+import { MapObject } from '../../layer/maputils';
+
+interface MapSelection {
+    order: string[];
+    maps: Object[];
+}
+
+@Component({
+    selector: 'onos-mapselector',
+    templateUrl: './mapselector.component.html',
+    styleUrls: ['./mapselector.component.css', './mapselector.theme.css', '../../topology.common.css']
+})
+export class MapSelectorComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy {
+    @Output() chosenMap = new EventEmitter<MapObject>();
+    lionFn; // Function
+    mapSelectorResponse: MapSelection = <MapSelection>{
+        order: [],
+        maps: []
+    };
+    form = new FormGroup({
+        mapid: new FormControl(this.mapSelectorResponse.order[0]),
+    });
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected ls: LoadingService,
+        protected wss: WebSocketService,
+        private lion: LionService
+    ) {
+        super(fs, ls, log, wss, 'topo');
+
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('topoms', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+
+        this.log.debug('Topo MapSelectorComponent constructed');
+    }
+
+    ngOnInit() {
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            ['mapSelectorResponse', (data) => {
+                this.mapSelectorResponse = data;
+                this.form.setValue({'mapid': this.mapSelectorResponse.order[0]});
+            }
+            ]
+        ]));
+        this.wss.sendEvent('mapSelectorRequest', {});
+        this.log.debug('Topo MapSelectorComponent initialized');
+    }
+
+    /**
+     * When the component is being unloaded then unbind the WSS handler.
+     */
+    ngOnDestroy(): void {
+        this.wss.unbindHandlers(['mapSelectorResponse']);
+        this.log.debug('Topo MapSelectorComponent destroyed');
+    }
+
+    /**
+     * Read the LION bundle for Details panel and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.Topo');
+    }
+
+    choice(mapid: Object): void {
+        this.chosenMap.emit(<MapObject>this.mapSelectorResponse.maps[mapid['mapid']]);
+    }
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.theme.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.theme.css
new file mode 100644
index 0000000..0ef1538
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/mapselector/mapselector.theme.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019-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.
+ */
+
+/**
+ * ONOS GUI -- Topology Map Selector theme -- CSS file
+ */
+
+/*.light */
+.dialog .dialog-button {
+    background-color: #518ecc;
+    color: white;
+}
+
+
+/* ========== DARK Theme ========== */
+
+.dark .dialog .dialog-button {
+    background-color: #345e85;
+    color: #cccccd;
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.css
new file mode 100644
index 0000000..b4bd37b
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-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.
+ */
+
+
+/*
+ ONOS GUI -- Topology Summary Panel -- CSS file
+ */
+#topo2-p-summary {
+    padding: 16px;
+    top: 100px;
+}
+
+#topo2-p-summary  td.label {
+    width: 50%;
+}
+
+#topo2-p div.header div.icon {
+    padding: 10px
+}
+
+#topo2-p-summary div.header h2 {
+    padding: 10px;
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.html b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.html
new file mode 100644
index 0000000..d781418
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.html
@@ -0,0 +1,20 @@
+<!--
+~ 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.
+-->
+<div id="topo2-p-summary" class="floatpanel topo2-p"
+     style="opacity: 1; right: 20px; width: 260px;" [@summaryPanelState]="on">
+    <!-- everything else is filled in dynamically by listProps() and the
+    response showSummary received from the server -->
+</div>
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.spec.ts
new file mode 100644
index 0000000..5f69c19
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.spec.ts
@@ -0,0 +1,84 @@
+/*
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+import { SummaryComponent } from './summary.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+import {
+    FnService,
+    LogService
+} from 'gui2-fw-lib';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+/**
+ * ONOS GUI -- Topology View Summary Panel -- Unit Tests
+ */
+describe('SummaryComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: SummaryComponent;
+    let fixture: ComponentFixture<SummaryComponent>;
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ SummaryComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(SummaryComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.ts
new file mode 100644
index 0000000..3a42a0b
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/summary/summary.component.ts
@@ -0,0 +1,125 @@
+/*
+ * 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 {
+    Component,
+    Input,
+    OnDestroy,
+    OnInit,
+    ViewEncapsulation
+} from '@angular/core';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import * as d3 from 'd3';
+import { TopoPanelBaseImpl } from '../topopanel.base';
+import {
+    LogService,
+    LoadingService,
+    FnService,
+    WebSocketService,
+    GlyphService
+} from 'gui2-fw-lib';
+
+export interface SummaryResponse {
+    title: string;
+}
+/**
+ * ONOS GUI -- Topology Summary Module.
+ * Defines modeling of ONOS Summary Panel.
+ * Note: This component uses the d3 DOM building technique from the old GUI - this
+ * is not the Angular way of building components and should be avoided generally
+ * See DetailsPanelComponent for a better way of doing this kind of thing
+ */
+@Component({
+    selector: 'onos-summary',
+    templateUrl: './summary.component.html',
+    styleUrls: [
+        './summary.component.css',
+        '../../topology.common.css', '../../topology.theme.css',
+        '../../../../fw/widget/panel.css', '../../../../fw/widget/panel-theme.css'
+    ],
+    encapsulation: ViewEncapsulation.None,
+    animations: [
+        trigger('summaryPanelState', [
+            state('true', style({
+                transform: 'translateX(0%)',
+                opacity: '100'
+            })),
+            state('false', style({
+                transform: 'translateX(100%)',
+                opacity: '0'
+            })),
+            transition('0 => 1', animate('100ms ease-in')),
+            transition('1 => 0', animate('100ms ease-out'))
+        ])
+    ]
+})
+export class SummaryComponent extends TopoPanelBaseImpl implements OnInit, OnDestroy {
+    @Input() on: boolean = false; // Override the parent class attribute
+    private handlers: string[] = [];
+    private resp: string = 'showSummary';
+    private summaryData: SummaryResponse;
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected ls: LoadingService,
+        protected wss: WebSocketService,
+        protected gs: GlyphService
+    ) {
+        super(fs, ls, log, 'summary');
+        this.summaryData = <SummaryResponse>{};
+        this.log.debug('SummaryComponent constructed');
+    }
+
+
+    ngOnInit() {
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            [this.resp, (data) => this.handleSummaryData(data)]
+        ]));
+        this.handlers.push(this.resp);
+
+        this.init(d3.select('#topo2-p-summary'));
+
+        this.wss.sendEvent('requestSummary', {});
+    }
+
+    ngOnDestroy() {
+        this.wss.sendEvent('cancelSummary', {});
+        this.wss.unbindHandlers(this.handlers);
+    }
+
+    handleSummaryData(data: SummaryResponse) {
+        this.summaryData = data;
+        this.render();
+    }
+
+    private render() {
+        let endedWithSeparator;
+
+        this.emptyRegions();
+
+        const svg = this.appendToHeader('div')
+                .classed('icon', true)
+                .append('svg');
+        const title = this.appendToHeader('h2');
+        const table = this.appendToBody('table');
+        const tbody = table.append('tbody');
+
+        title.text(this.summaryData.title);
+        this.gs.addGlyph(svg, 'bird', 24, 0, [1, 1]);
+        endedWithSeparator = this.listProps(tbody, this.summaryData);
+        // TODO : review whether we need to use/store end-with-sep state
+    }
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/button.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/button.css
new file mode 100644
index 0000000..1effdbc
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/button.css
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/*
+ ONOS GUI -- Button Service (layout) -- CSS file
+ */
+
+.button,
+.toggleButton,
+.radioSet {
+    display: inline-block;
+    padding: 0 4px;
+}
+.radioButton {
+    display: inline-block;
+    padding: 0 2px;
+}
+
+.button svg.embeddedIcon,
+.toggleButton svg.embeddedIcon,
+.radioButton svg.embeddedIcon {
+    cursor: pointer;
+}
+.button svg.embeddedIcon .icon rect,
+.toggleButton svg.embeddedIcon .icon rect,
+.radioButton svg.embeddedIcon .icon rect{
+    stroke: none;
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.css
new file mode 100644
index 0000000..449d436
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.css
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-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.
+ */
+
+
+/*
+ ONOS GUI -- Topology Toolbar Panel -- CSS file
+ */
+
+
+
+div.tbar-arrow {
+    position: absolute;
+    top: 53%;
+    left: 96%;
+    margin-right: -4%;
+    transform: translate(-50%, -50%);
+    cursor: pointer;
+}
+.safari div.tbar-arrow {
+    top: 46%;
+}
+.firefox div.tbar-arrow {
+    left: 97%;
+    margin-right: -3%;
+}
+
+.toolbar {
+    line-height: 125%;
+}
+.tbar-row {
+    display: inline-block;
+}
+
+.separator {
+    border: 1px solid;
+    margin: 0 4px 0 4px;
+    display: inline-block;
+    height: 23px;
+    width: 0;
+}
+
+#toolbar-topo2-toolbar {
+    padding: 6px;
+}
+
+#toolbar-topo2-toolbar .tbar-row.right {
+    width: 100%;
+}
+
+#toolbar-topo2-toolbar .tbar-row-text {
+    height: 21px;
+    text-align: right;
+    padding: 8px 60px 0 0;
+    font-style: italic;
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html
new file mode 100644
index 0000000..623c425
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.html
@@ -0,0 +1,77 @@
+<!--
+~ 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.
+-->
+<div id="toolbar-topo2-toolbar" class="floatpanel toolbar" [@toolbarState]="on"
+     style="opacity: 1; left: 0px; width: 286px; top: auto; bottom: 10px;">
+    <div class="tbar-arrow" (click)="on =! on">
+        <onos-icon [iconSize]="10" [iconId]="on?'triangleLeft':'triangleRight'"></onos-icon>
+    </div>
+    <div class="tbar-row ctrl-btns">
+        <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-instance-tog" (click)="buttonClicked('instance-tog')">
+            <onos-icon [iconSize]="25" iconId="m_uiAttached" [toolTip]="lionFn('tbtt_tog_instances')" [classes]="['toggleButton', instancesVisible?'selected':'']"></onos-icon>
+        </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-summary-tog" (click)="buttonClicked('summary-tog')">
+            <onos-icon [iconSize]="25" iconId="m_summary" [toolTip]="lionFn('tbtt_tog_summary')" [classes]="['toggleButton', summaryVisible?'selected':'']"></onos-icon>
+        </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-details-tog" (click)="buttonClicked('details-tog')">
+            <onos-icon [iconSize]="25" iconId="m_details" [toolTip]="lionFn('tbtt_tog_use_detail')" [classes]="['toggleButton', detailsVisible?'selected':'']"></onos-icon>
+        </div>
+        <div class="separator"></div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-hosts-tog" (click)="buttonClicked('hosts-tog')">
+            <onos-icon [iconSize]="25" iconId="m_endstation" [toolTip]="lionFn('tbtt_tog_host')" [classes]="['toggleButton', hostsVisible?'selected':'']"></onos-icon>
+        </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-offline-tog" (click)="buttonClicked('offline-tog')">
+            <onos-icon [iconSize]="25" iconId="m_switch" [toolTip]="lionFn('tbtt_tog_offline')" classes="toggleButton selected"></onos-icon>
+        </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-ports-tog" (click)="buttonClicked('ports-tog')">
+            <onos-icon [iconSize]="25" iconId="m_ports" [toolTip]="lionFn('tbtt_tog_porthi')" classes="toggleButton selected" [classes]="['toggleButton', portsVisible?'selected':'']"></onos-icon>
+        </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-bkgrnd-tog" (click)="buttonClicked('bkgrnd-tog')">
+            <onos-icon [iconSize]="25" iconId="m_map" [toolTip]="lionFn('tbtt_tog_map')" classes="toggleButton selected" [classes]="['toggleButton', backgroundVisible?'selected':'']"></onos-icon>
+        </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-bkgrnd-sel" (click)="buttonClicked('bkgrnd-sel')">
+            <onos-icon [iconSize]="25" iconId="m_selectMap" [toolTip]="lionFn('tbtt_sel_map')" classes="button"></onos-icon>
+        </div>
+    </div>
+    <br>
+    <div class="tbar-row">
+        <div class="button" id="toolbar-topo2-toolbar-topo2-cycleLabels-btn" (click)="buttonClicked('cycleLabels-btn')">
+            <onos-icon [iconSize]="25" iconId="m_cycleLabels" [toolTip]="lionFn('tbtt_cyc_dev_labs')" classes="button"></onos-icon>
+        </div>
+        <div class="button" id="toolbar-topo2-toolbar-topo2-resetZoom-btn" (click)="buttonClicked('resetZoom-btn')">
+            <onos-icon [iconSize]="25" iconId="m_resetZoom" [toolTip]="lionFn('tbtt_reset_zoom')" classes="button"></onos-icon>
+        </div>
+        <div class="separator"></div>
+        <div class="button" id="toolbar-topo2-toolbar-topo2-eqMaster-btn" (click)="buttonClicked('eqMaster-btn')">
+            <onos-icon [iconSize]="25" iconId="m_eqMaster" [toolTip]="lionFn('tbtt_eq_master')" classes="button"></onos-icon>
+        </div>
+        <div class="separator"></div>
+        <div class="radioSet" id="toolbar-topo2-traffic">
+            <div class="radioButton selected" id="toolbar-topo2-cancel-traffic" (click)="buttonClicked('cancel-traffic')">
+                <onos-icon [iconSize]="25" iconId="m_unknown" [toolTip]="lionFn('tr_btn_cancel_monitoring')" classes="radioButton selected"></onos-icon>
+            </div>
+            <div class="radioButton" id="toolbar-topo2-all-traffic" (click)="buttonClicked('all-traffic')">
+                <onos-icon [iconSize]="25" iconId="m_allTraffic" [toolTip]="lionFn('tr_btn_show_related_traffic')" classes="radioButton selected"></onos-icon>
+            </div>
+        </div>
+        <div class="separator"></div>
+        <div class="button" id="toolbar-topo2-toolbar-topo2-quickhelp" (click)="buttonClicked('quickhelp-btn')">
+            <onos-icon [iconSize]="25" iconId="query" [toolTip]="lionFn('qh_title')" classes="button"></onos-icon>
+        </div>
+        <div class="button" id="toolbar-topo2-toolbar-topo2-cycleGrid-btn" (click)="buttonClicked('cycleGridDisplay-btn')">
+            <onos-icon [iconSize]="25" iconId="m_cycleGridDisplay" [toolTip]="lionFn('tbtt_cyc_grid_display')" classes="button"></onos-icon>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.spec.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.spec.ts
new file mode 100644
index 0000000..91c1de4
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.spec.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { of } from 'rxjs';
+import { ToolbarComponent } from './toolbar.component';
+
+import {
+    FnService, LionService,
+    LogService, IconComponent
+} from 'gui2-fw-lib';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+/**
+ * ONOS GUI -- Topology View Topology Panel-- Unit Tests
+ */
+describe('ToolbarComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: ToolbarComponent;
+    let fixture: ComponentFixture<ToolbarComponent>;
+
+    const bundleObj = {
+        'core.view.Topo': {
+            test: 'test1'
+        }
+    };
+    const mockLion = (key) => {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ ToolbarComponent, IconComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                {
+                    provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(ToolbarComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts
new file mode 100644
index 0000000..0615530
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.component.ts
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019-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 {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {
+    LogService,
+    LoadingService,
+    FnService,
+    PanelBaseImpl, LionService
+} from 'gui2-fw-lib';
+
+import {animate, state, style, transition, trigger} from '@angular/animations';
+
+export const INSTANCE_TOGGLE = 'instance-tog';
+export const SUMMARY_TOGGLE = 'summary-tog';
+export const DETAILS_TOGGLE = 'details-tog';
+export const HOSTS_TOGGLE = 'hosts-tog';
+export const OFFLINE_TOGGLE = 'offline-tog';
+export const PORTS_TOGGLE = 'ports-tog';
+export const BKGRND_TOGGLE = 'bkgrnd-tog';
+export const BKGRND_SELECT = 'bkgrnd-sel';
+export const CYCLELABELS_BTN = 'cycleLabels-btn';
+export const CYCLEHOSTLABEL_BTN = 'cycleHostLabel-btn';
+export const CYCLEGRIDDISPLAY_BTN = 'cycleGridDisplay-btn';
+export const RESETZOOM_BTN = 'resetZoom-btn';
+export const EQMASTER_BTN = 'eqMaster-btn';
+export const CANCEL_TRAFFIC = 'cancel-traffic';
+export const ALL_TRAFFIC = 'all-traffic';
+export const QUICKHELP_BTN = 'quickhelp-btn';
+
+
+/*
+ ONOS GUI -- Topology Toolbar Module.
+ Defines modeling of ONOS toolbar.
+ */
+@Component({
+    selector: 'onos-toolbar',
+    templateUrl: './toolbar.component.html',
+    styleUrls: [
+        './toolbar.component.css', './toolbar.theme.css',
+        '../../topology.common.css',
+        '../../../../fw/widget/panel.css', '../../../../fw/widget/panel-theme.css',
+        './button.css'
+    ],
+    animations: [
+        trigger('toolbarState', [
+            state('true', style({
+                transform: 'translateX(0%)',
+                // opacity: '1.0'
+            })),
+            state('false', style({
+                transform: 'translateX(-93%)',
+                // opacity: '0.0'
+            })),
+            transition('0 => 1', animate('500ms ease-in')),
+            transition('1 => 0', animate('500ms ease-out'))
+        ])
+    ]
+})
+export class ToolbarComponent extends PanelBaseImpl {
+    @Input() on: boolean = false; // Override the parent class attribute
+    // deferred localization strings
+    lionFn; // Function
+    // Used to drive the display of the hosts icon - there is also another such variable on the forcesvg
+    @Input() hostsVisible: boolean = false;
+    @Input() instancesVisible: boolean = true;
+    @Input() summaryVisible: boolean = true;
+    @Input() detailsVisible: boolean = true;
+    @Input() backgroundVisible: boolean = false;
+    @Input() portsVisible: boolean = true;
+
+    @Output() buttonEvent = new EventEmitter<string>();
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected ls: LoadingService,
+        private lion: LionService
+    ) {
+        super(fs, ls, log);
+
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('topo-toolbar', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+
+        this.log.debug('ToolbarComponent constructed');
+    }
+
+    /**
+     * Read the LION bundle for Toolbar and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.Topo');
+    }
+
+    /**
+     * As buttons are clicked on the toolbar, emit events up to the parent
+     *
+     * The toggling of the input variables here is in addition to the control
+     * of these input variables from the parent. This is so that this component
+     * may be reused and is not dependent on a particular parent implementation
+     * to work
+     * @param name The name of button clicked.
+     */
+    buttonClicked(name: string): void {
+        switch (name) {
+            case HOSTS_TOGGLE:
+                this.hostsVisible = !this.hostsVisible;
+                break;
+            case INSTANCE_TOGGLE:
+                this.instancesVisible = !this.instancesVisible;
+                break;
+            case SUMMARY_TOGGLE:
+                this.summaryVisible = !this.summaryVisible;
+                break;
+            case DETAILS_TOGGLE:
+                this.detailsVisible = !this.detailsVisible;
+                break;
+            case BKGRND_TOGGLE:
+                this.backgroundVisible = !this.backgroundVisible;
+                break;
+            default:
+        }
+        // Send a message up to let TopologyComponent know of the event
+        this.buttonEvent.emit(name);
+    }
+}
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.theme.css b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.theme.css
new file mode 100644
index 0000000..7933ee6
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/toolbar/toolbar.theme.css
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016-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.
+ */
+
+
+/**
+ * ONOS GUI -- Topology Toolbar Panel -- Theme CSS file
+ */
+.tbar-arrow svg.embeddedIcon .icon rect {
+    stroke: none;
+}
+
+.tbar-arrow svg.embeddedIcon .icon .glyph {
+    fill: #838383;
+}
+
+.tbar-arrow svg.embeddedIcon .icon rect {
+    fill: none;
+}
+
+.separator {
+    border-color: #ddd;
+}
+
+/* ========== DARK Theme ========== */
+
+.dark .tbar-arrow svg.embeddedIcon .icon .glyph {
+    fill: #B2B2B2;
+}
+
+.dark .tbar-arrow svg.embeddedIcon .icon rect {
+    fill: none;
+}
+
+.dark .separator {
+    border-color: #454545;
+}
+
+.dark #toolbar-topo2-toolbar .tbar-row.right {
+    color: #666;
+}
\ No newline at end of file
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/topopanel.base.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/topopanel.base.ts
new file mode 100644
index 0000000..48aa2ed
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/panel/topopanel.base.ts
@@ -0,0 +1,109 @@
+/*
+ * 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 {
+    FnService,
+    LoadingService,
+    LogService,
+    PanelBaseImpl
+} from 'gui2-fw-lib';
+
+/**
+ * Base model of panel view - implemented by Topology Panel components
+ */
+export abstract class TopoPanelBaseImpl extends PanelBaseImpl {
+
+    protected header: any;
+    protected body: any;
+    protected footer: any;
+
+    protected constructor(
+        protected fs: FnService,
+        protected ls: LoadingService,
+        protected log: LogService,
+        protected id: string
+    ) {
+        super(fs, ls, log);
+    }
+
+    protected init(el: any) {
+        this.header = el.append('div').classed('header', true);
+        this.body = el.append('div').classed('body', true);
+        this.footer = el.append('div').classed('footer', true);
+    }
+
+    /**
+     * Decode lists of props sent back through Web Socket
+     *
+     * Means that panels do not have to know property names in advance
+     * Driven by PropertyPanel on Server side
+     */
+    listProps(el, data) {
+        let sepLast: boolean = false;
+
+        // note: track whether we end with a separator or not...
+        data.propOrder.forEach((p) => {
+            if (p === '-') {
+                this.addSep(el);
+                sepLast = true;
+            } else {
+                this.addProp(el, data.propLabels[p], data.propValues[p]);
+                sepLast = false;
+            }
+        });
+        return sepLast;
+    }
+
+    addProp(el, label, value) {
+        const tr = el.append('tr');
+        let lab;
+
+        if (typeof label === 'string') {
+            lab = label.replace(/_/g, ' ');
+        } else {
+            lab = label;
+        }
+
+        function addCell(cls, txt) {
+            tr.append('td').attr('class', cls).text(txt);
+        }
+
+        addCell('label', lab + ' :');
+        addCell('value', value);
+    }
+
+    addSep(el) {
+        el.append('tr').append('td').attr('colspan', 2).append('hr');
+    }
+
+    appendToHeader(x) {
+        return this.header.append(x);
+    }
+
+    appendToBody(x) {
+        return this.body.append(x);
+    }
+
+    appendToFooter(x) {
+        return this.footer.append(x);
+    }
+
+    emptyRegions() {
+        this.header.selectAll('*').remove();
+        this.body.selectAll('*').remove();
+        this.footer.selectAll('*').remove();
+    }
+
+}