Added native Bazel build to GUI2. Reduced a lot of the unused Angular CLI structures

Reviewers should look at the changes in WORKSPACE, BUILD, BUILD.bazel, README.md files
This is only possible now as rules_nodejs went to 1.0.0 on December 20
gui2 has now been made the entry point (rather than gui2-fw-lib)
No tests or linting are functional yet for Typescript
Each NgModule now has its own BUILD.bazel file with ng_module
gui2-fw-lib is all one module and has been refactored to simplify the directory structure
gui2-topo-lib is also all one module - its directory structure has had 3 layers removed
The big bash script in web/gui2/BUILD has been removed - all is done through ng_module rules
in web/gui2/src/main/webapp/BUILD.bazel and web/gui2/src/main/webapp/app/BUILD.bazel

Change-Id: Ifcfcc23a87be39fe6d6c8324046cc8ebadb90551
diff --git a/web/gui2-fw-lib/lib/layer/confirm/confirm.component.css b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.css
new file mode 100644
index 0000000..b097534
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.css
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Confirm Component (layout) -- CSS file
+ */
+#app-dialog {
+    top: 140px;
+    padding: 12px;
+}
+
+#app-dialog h3 {
+    display: inline-block;
+    font-weight: bold;
+    font-size: 18pt;
+}
+
+#app-dialog p {
+    font-size: 12pt;
+}
+
+#app-dialog p.strong {
+    font-weight: bold;
+    padding: 8px;
+    text-align: center;
+}
diff --git a/web/gui2-fw-lib/lib/layer/confirm/confirm.component.html b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.html
new file mode 100644
index 0000000..098d83a
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.html
@@ -0,0 +1,22 @@
+<!--
+~ 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="app-dialog" class="floatpanel dialog" [@confirmDlgState]="message!==''">
+    <h3> {{ title }} </h3>
+    <p>{{ message }}</p>
+    <p *ngIf="warning" class="warning strong">{{ warning }}</p>
+    <div tabindex="10" class="dialog-button" (click)="choice(true)">OK</div>
+    <div tabindex="11" class="dialog-button" (click)="choice(false)">Cancel</div>
+</div>
diff --git a/web/gui2-fw-lib/lib/layer/confirm/confirm.component.spec.ts b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.spec.ts
new file mode 100644
index 0000000..9692e3a
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.spec.ts
@@ -0,0 +1,90 @@
+/*
+ * 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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { LionService } from '../../util/lion.service';
+
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { LogService } from '../../log.service';
+import { ConfirmComponent } from './confirm.component';
+
+/**
+ * ONOS GUI -- Layer -- Confirm Component - Unit Tests
+ */
+describe('ConfirmComponent', () => {
+    let log: LogService;
+    let component: ConfirmComponent;
+    let fixture: ComponentFixture<ConfirmComponent>;
+    const bundleObj = {
+        'core.view.App': {
+            test: 'test1',
+            dlg_confirm_action: 'Confirm'
+        }
+    };
+    const mockLion = (key) => {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ ConfirmComponent ],
+            providers: [
+                { provide: LogService, useValue: log },
+                {
+                    provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+            ]
+        });
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(ConfirmComponent);
+        component = fixture.debugElement.componentInstance;
+        component.title = 'Confirm';
+        component.message = 'A message';
+        component.warning = 'A warning';
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should have a h3 inside a div#app-dialog', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#app-dialog h3'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual(' Confirm ');
+    });
+
+    it('should have a div.dialog-button inside a div#app-dialog', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#app-dialog div.dialog-button'));
+        const div: HTMLElement = divDe.nativeElement;
+        // It selects the first one
+        expect(div.textContent).toEqual('OK');
+    });
+});
diff --git a/web/gui2-fw-lib/lib/layer/confirm/confirm.component.ts b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.ts
new file mode 100644
index 0000000..7533a7e
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/confirm/confirm.component.ts
@@ -0,0 +1,88 @@
+/*
+ * 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 { trigger, state, style, animate, transition } from '@angular/animations';
+import { LogService } from '../../log.service';
+import { LionService } from '../../util/lion.service';
+
+/**
+ * ONOS GUI -- Layer -- Confirm Component
+ *
+ * Replaces Flash Service in old GUI.
+ * Provides a mechanism to present a confirm dialog to the screen
+ *
+ * To use add an element to the template like
+ *   <onos-confirm message="Performing something dangerous. Would you like to proceed"></onos-flash>
+ *
+ * An event is raised with either OK or Cancel
+ */
+@Component({
+    selector: 'onos-confirm',
+    templateUrl: './confirm.component.html',
+    styleUrls: [
+        './confirm.component.css',
+        './confirm.theme.css',
+        '../dialog.css',
+        '../dialog.theme.css',
+        '../../widget/panel.css',
+        '../../widget/panel-theme.css'
+    ],
+    animations: [
+        trigger('confirmDlgState', [
+            state('true', style({
+                transform: 'translateX(-100%)',
+                opacity: '100'
+            })),
+            state('false', style({
+                transform: 'translateX(0%)',
+                opacity: '0'
+            })),
+            transition('0 => 1', animate('100ms ease-in')),
+            transition('1 => 0', animate('100ms ease-out'))
+        ])
+    ]
+})
+export class ConfirmComponent {
+
+    lionFn; // Function
+
+    @Input() message: string;
+    @Input() warning: string;
+    @Input() title: string;
+    @Output() chosen: EventEmitter<boolean> = new EventEmitter();
+
+    constructor(
+        private log: LogService,
+        private lion: LionService,
+    ) {
+        this.log.debug('ConfirmComponent constructed');
+        this.doLion();
+    }
+
+    /**
+     * When OK or Cancel is pressed, send an event to parent with choice
+     */
+    choice(chosen: boolean): void {
+        this.chosen.emit(chosen);
+    }
+
+    /**
+     * Read the LION bundle for App to get confirm dialouge header
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.App');
+    }
+}
diff --git a/web/gui2-fw-lib/lib/layer/confirm/confirm.theme.css b/web/gui2-fw-lib/lib/layer/confirm/confirm.theme.css
new file mode 100644
index 0000000..db97648
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/confirm/confirm.theme.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.
+ */
+
+/*
+ ONOS GUI -- Confirm Component (theme) -- CSS file
+ */
+/* temporarily removed .light */
+#app-dialog p.strong {
+    color: white;
+    background-color: #ce5b58;
+}
+
+#app-dialog.floatpanel.dialog {
+    background-color: #ffffff;
+}
+
+#app-dialog p.strong {
+    color: white;
+    background-color: #ce5b58;
+}
diff --git a/web/gui2-fw-lib/lib/layer/dialog.css b/web/gui2-fw-lib/lib/layer/dialog.css
new file mode 100644
index 0000000..6792a3d
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/dialog.css
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Dialog Service (layout) -- 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-fw-lib/lib/layer/dialog.theme.css b/web/gui2-fw-lib/lib/layer/dialog.theme.css
new file mode 100644
index 0000000..24cd5fa
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/dialog.theme.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.
+ */
+
+/*
+ ONOS GUI -- Dialog Service (theme) -- CSS file
+ */
+
+/* temporarily removed .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-fw-lib/lib/layer/flash/flash.component.css b/web/gui2-fw-lib/lib/layer/flash/flash.component.css
new file mode 100644
index 0000000..66b8f3b
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/flash/flash.component.css
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Flash Component (layout) -- CSS file
+ */
+
+#flash {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    z-index: 1400;
+}
+
+#flash.warning div#flashBox {
+    border: 2px solid #222222;
+    border-radius: 10px;
+    background: #FFFFFF;
+    padding: 10px;
+}
+
+#flash div#flashBox {
+    background: #CCCCCC;
+    border-radius: 10px;
+    padding: 1px;
+}
+
+#flash div#flashBox div.dialog-button {
+    transform :translateY(-32px);
+}
+
+#flash.warning p#flashText {
+    stroke: #FF0000;
+    color: #FF0000;
+    text-anchor: middle;
+    alignment-baseline: middle;
+    text-align: center;
+    font-size: 16pt;
+    border-radius: 10px;
+    background: #FFFFFF;
+    padding: 10px;
+}
+
+#flash p#flashText {
+    stroke: none;
+    color: #222222;
+    text-anchor: middle;
+    alignment-baseline: middle;
+    text-align: center;
+    font-size: 16pt;
+    border-radius: 10px;
+    background: #CCCCCC;
+    padding: 5px;
+}
diff --git a/web/gui2-fw-lib/lib/layer/flash/flash.component.html b/web/gui2-fw-lib/lib/layer/flash/flash.component.html
new file mode 100644
index 0000000..46b6de9
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/flash/flash.component.html
@@ -0,0 +1,21 @@
+<!--
+~ 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="flash" class="dialog" [ngClass]="warning?'warning':''" [@flashState]="visible">
+    <div id="flashBox" *ngIf="visible">
+        <p id="flashText">{{ message }}</p>
+        <div class="dialog-button" *ngIf="dwell>1200" (click)="closeNow()">Dismiss</div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/web/gui2-fw-lib/lib/layer/flash/flash.component.spec.ts b/web/gui2-fw-lib/lib/layer/flash/flash.component.spec.ts
new file mode 100644
index 0000000..661e223
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/flash/flash.component.spec.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { LogService } from '../../log.service';
+import { FlashComponent } from './flash.component';
+
+/**
+ * ONOS GUI -- Layer -- Flash Component - Unit Tests
+ */
+describe('FlashComponent', () => {
+    let log: LogService;
+    let component: FlashComponent;
+    let fixture: ComponentFixture<FlashComponent>;
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ FlashComponent ],
+            providers: [
+                { provide: LogService, useValue: log },
+            ]
+        });
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(FlashComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+//    it('should have a div#flash', () => {
+//        component.enabled = true;
+//        const appDe: DebugElement = fixture.debugElement;
+//        const divDe = appDe.query(By.css('div#flash'));
+//        expect(divDe).toBeTruthy();
+//    });
+});
diff --git a/web/gui2-fw-lib/lib/layer/flash/flash.component.ts b/web/gui2-fw-lib/lib/layer/flash/flash.component.ts
new file mode 100644
index 0000000..7dd721d
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/flash/flash.component.ts
@@ -0,0 +1,91 @@
+/*
+ * 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, OnChanges, SimpleChange, EventEmitter } from '@angular/core';
+import { LogService } from '../../log.service';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+
+/**
+ * ONOS GUI -- Layer -- Flash Component
+ *
+ * Replaces Flash Service in old GUI.
+ * Provides a mechanism to flash short informational messages to the screen
+ * to alert the user of something, e.g. "Hosts visible" or "Hosts hidden".
+ *
+ * It can be used in a warning mode, where text will appear in red
+ * The dwell time (milliseconds) can be controlled or the default is 1200ms
+ *
+ * To use add an element to the template like
+ *   <onos-flash message="Hosts visible" dwell="2000" warning="true"></onos-flash>
+ * This whole element can be disabled until needed with an ngIf, but if this is done
+ * the animated fade-in and fade-out will not happen
+ * There is also a (closed) event that tells you when the message is closed, or
+ * fades-out
+ */
+@Component({
+    selector: 'onos-flash',
+    templateUrl: './flash.component.html',
+    styleUrls: [
+        './flash.component.css',
+        '../dialog.css',
+        '../dialog.theme.css',
+    ],
+    animations: [
+        trigger('flashState', [
+            state('false', style({
+//                transform: 'translateY(-400%)',
+                opacity: '0.0',
+            })),
+            state('true', style({
+//                transform: 'translateY(0%)',
+                opacity: '1.0',
+            })),
+            transition('0 => 1', animate('200ms ease-in')),
+            transition('1 => 0', animate('200ms ease-out'))
+        ])
+    ]
+})
+export class FlashComponent implements OnChanges {
+    @Input() message: string;
+    @Input() dwell: number = 1200; // milliseconds
+    @Input() warning: boolean = false;
+    @Output() closed: EventEmitter<boolean> = new EventEmitter();
+
+    public visible: boolean = false;
+
+    /**
+     * Flash a message up for 1200ms then disappear again.
+     * See animation parameter for the ease in and ease out params
+     */
+    ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
+        if (changes['message'] && this.message && this.message !== '') {
+            this.visible = true;
+
+            setTimeout(() => {
+                this.visible = false;
+                this.closed.emit(false);
+            }, this.dwell);
+        }
+    }
+
+    /**
+     * The message will flash up for 'dwell' milliseconds
+     * If dwell is > 2000ms, then there will be a button that allows it to be dismissed now
+     */
+    closeNow() {
+        this.visible = false;
+        this.closed.emit(false);
+    }
+}
diff --git a/web/gui2-fw-lib/lib/layer/loading/loading.component.css b/web/gui2-fw-lib/lib/layer/loading/loading.component.css
new file mode 100644
index 0000000..9095bd2
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/loading/loading.component.css
@@ -0,0 +1,28 @@
+/*
+ *  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 -- Loading Service -- CSS file
+ */
+
+#loading-anim {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    -webkit-transform: translate(-50%, -50%);
+    transform: translate(-50%, -50%);
+    z-index: -5000;
+}
diff --git a/web/gui2-fw-lib/lib/layer/loading/loading.component.html b/web/gui2-fw-lib/lib/layer/loading/loading.component.html
new file mode 100644
index 0000000..8220197
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/loading/loading.component.html
@@ -0,0 +1,16 @@
+<!--
+~ 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.
+-->
+<img id="loading-anim" [src]="img" alt="loading - please wait" [@loadingState]="running"/>
diff --git a/web/gui2-fw-lib/lib/layer/loading/loading.component.spec.ts b/web/gui2-fw-lib/lib/layer/loading/loading.component.spec.ts
new file mode 100644
index 0000000..185c20e
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/loading/loading.component.spec.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { LoadingComponent } from './loading.component';
+import {LogService} from '../../log.service';
+import {ConsoleLoggerService} from '../../consolelogger.service';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+
+describe('LoadingComponent', () => {
+    let log: LogService;
+    let component: LoadingComponent;
+    let fixture: ComponentFixture<LoadingComponent>;
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ LoadingComponent ],
+            providers: [
+                { provide: LogService, useValue: log }
+            ]
+        })
+        .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(LoadingComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-fw-lib/lib/layer/loading/loading.component.ts b/web/gui2-fw-lib/lib/layer/loading/loading.component.ts
new file mode 100644
index 0000000..59ce04b
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/loading/loading.component.ts
@@ -0,0 +1,110 @@
+/*
+ * 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 {LogService} from '../../log.service';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+
+const LOADING_IMG_DIR = 'data/img/loading/';
+const LOADING_PFX = '/load-';
+const NUM_IMGS = 16;
+
+/**
+ * ONOS GUI - A component that shows the loading icon
+ *
+ * Should be shown if someone has to wait for more than
+ * a certain time for data to be retrieved
+ * Note the animation - there is a pause of 500ms before the images appear
+ * and then it eases in over 200ms
+ */
+@Component({
+    selector: 'onos-loading',
+    templateUrl: './loading.component.html',
+    styleUrls: ['./loading.component.css'],
+    animations: [
+        trigger('loadingState', [
+            state('false', style({
+                opacity: '0.0',
+                'z-index': -5000
+            })),
+            state('true', style({
+                opacity: '1.0',
+                'z-index': 5000
+            })),
+            transition('0 => 1', animate('200ms 500ms ease-in')),
+            transition('1 => 0', animate('200ms ease-out'))
+        ])
+    ]
+})
+export class LoadingComponent implements OnChanges {
+    @Input() theme: string = 'light';
+    @Input() running: boolean;
+
+    speed: number = 8; // Frames per second
+    idx = 1;
+    images: HTMLImageElement[] = [];
+    img: string;
+    task: any;
+
+    constructor(
+        private log: LogService,
+    ) {
+        let idx: number;
+
+        for (idx = 1; idx <= NUM_IMGS ; idx++) {
+            this.addImg('light', idx);
+            this.addImg('dark', idx);
+        }
+
+        this.log.debug('LoadingComponent constructed - images preloaded from', this.fname(1, this.theme));
+    }
+
+    /**
+     * Detects changes in in Input variable
+     * Here we want to detect if running has been enabled or disabled
+     * @param changes
+     */
+    ngOnChanges(changes: SimpleChanges): void {
+        if (changes['running']) {
+            const newRunning: boolean = changes['running'].currentValue;
+
+            if (newRunning) {
+                this.task = setInterval(() => this.nextFrame(), 1000 / this.speed);
+            } else {
+                if (this.task) {
+                    clearInterval(this.task);
+                    this.task = null;
+                }
+            }
+        }
+    }
+
+    private addImg(theme: string, idx: number): void {
+        const img = new Image();
+        img.src = this.fname(idx, theme);
+        this.images.push(img);
+    }
+
+    private fname(i: number, theme: string): string {
+        const z = i > 9 ? '' : '0';
+        return LOADING_IMG_DIR + theme + LOADING_PFX + z + i + '.png';
+    }
+
+    private nextFrame(): void {
+        this.idx = this.idx === 16 ? 1 : this.idx + 1;
+        this.img  = this.fname(this.idx, this.theme);
+    }
+
+}
diff --git a/web/gui2-fw-lib/lib/layer/panel.service.ts b/web/gui2-fw-lib/lib/layer/panel.service.ts
new file mode 100644
index 0000000..863d18f
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/panel.service.ts
@@ -0,0 +1,222 @@
+/*
+ *  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 {Injectable} from '@angular/core';
+import {FnService} from '../util/fn.service';
+import {LogService} from '../log.service';
+import {ThemeService} from '../util/theme.service';
+import {WebSocketService} from '../remote/websocket.service';
+import * as d3 from 'd3';
+
+let fs;
+
+const defaultSettings = {
+    edge: 'right',
+    width: 200,
+    margin: 20,
+    hideMargin: 20,
+    xtnTime: 750,
+    fade: true,
+};
+
+let panels,
+    panelLayer;
+
+function init() {
+    panelLayer = d3.select('div#floatpanels');
+    panelLayer.text('');
+    panels = {};
+}
+
+// helpers for panel
+function noop() {
+}
+
+function margin(p: any) {
+    return p.settings.margin;
+}
+
+function hideMargin(p: any) {
+    return p.settings.hideMargin;
+}
+
+function noPx(p: any, what: any) {
+    return Number(p.el.style(what).replace(/px$/, ''));
+}
+
+function widthVal(p: any) {
+    return noPx(p, 'width');
+}
+
+function heightVal(p: any) {
+    return noPx(p, 'height');
+}
+
+function pxShow(p: any) {
+    return margin(p) + 'px';
+}
+
+function pxHide(p: any) {
+    return (-hideMargin(p) - widthVal(p) - (noPx(p, 'padding') * 2)) + 'px';
+}
+
+function makePanel(id: any, settings: any) {
+    const p = {
+            id: id,
+            settings: settings,
+            on: false,
+            el: null,
+        },
+        api = {
+            show: showPanel,
+            hide: hidePanel,
+            toggle: togglePanel,
+            empty: emptyPanel,
+            append: appendPanel,
+            width: panelWidth,
+            height: panelHeight,
+            bbox: panelBBox,
+            isVisible: panelIsVisible,
+            classed: classed,
+            el: panelEl,
+        };
+
+    p.el = panelLayer.append('div')
+        .attr('id', id)
+        .attr('class', 'floatpanel')
+        .style('opacity', 0);
+
+    // has to be called after el is set
+    p.el.style(p.settings.edge, pxHide(p));
+    panelWidth(p.settings.width);
+    if (p.settings.height) {
+        panelHeight(p.settings.height);
+    }
+
+    panels[id] = p;
+
+    function showPanel(cb: any) {
+        const endCb = fs.isF(cb) || noop;
+        p.on = true;
+        p.el.transition().duration(p.settings.xtnTime)
+            .style(p.settings.edge, pxShow(p))
+            .style('opacity', 1);
+    }
+
+    function hidePanel(cb: any) {
+        const endCb = fs.isF(cb) || noop,
+            endOpacity = p.settings.fade ? 0 : 1;
+        p.on = false;
+        p.el.transition().duration(p.settings.xtnTime)
+            .style(p.settings.edge, pxHide(p))
+            .style('opacity', endOpacity);
+    }
+
+    function togglePanel(cb: any) {
+        if (p.on) {
+            hidePanel(cb);
+        } else {
+            showPanel(cb);
+        }
+        return p.on;
+    }
+
+    function emptyPanel() {
+        return p.el.text('');
+    }
+
+    function appendPanel(what: any) {
+        return p.el.append(what);
+    }
+
+    function panelWidth(w: any) {
+        if (w === undefined) {
+            return widthVal(p);
+        }
+        p.el.style('width', w + 'px');
+    }
+
+    function panelHeight(h: any) {
+        if (h === undefined) {
+            return heightVal(p);
+        }
+        p.el.style('height', h + 'px');
+    }
+
+    function panelBBox() {
+        return p.el.node().getBoundingClientRect();
+    }
+
+    function panelIsVisible() {
+        return p.on;
+    }
+
+    function classed(cls: any, bool: any) {
+        return p.el.classed(cls, bool);
+    }
+
+    function panelEl() {
+        return p.el;
+    }
+
+    return api;
+}
+
+function removePanel(id: any) {
+    panelLayer.select('#' + id).remove();
+    delete panels[id];
+}
+
+@Injectable({
+    providedIn: 'root',
+})
+export class PanelService {
+    constructor(private funcs: FnService,
+                private log: LogService,
+                private ts: ThemeService,
+                private wss: WebSocketService) {
+        fs = this.funcs;
+        init();
+    }
+
+    createPanel(id: any, opts: any) {
+        const settings = (<any>Object).assign({}, defaultSettings, opts);
+        if (!id) {
+            this.log.warn('createPanel: no ID given');
+            return null;
+        }
+        if (panels[id]) {
+            this.log.warn('Panel with ID "' + id + '" already exists');
+            return null;
+        }
+        if (fs.debugOn('widget')) {
+            this.log.debug('creating panel:', id, settings);
+        }
+        return makePanel(id, settings);
+    }
+
+    destroyPanel(id: any) {
+        if (panels[id]) {
+            if (fs.debugOn('widget')) {
+                this.log.debug('destroying panel:', id);
+            }
+            removePanel(id);
+        } else {
+            if (fs.debugOn('widget')) {
+                this.log.debug('no panel to destroy:', id);
+            }
+        }
+    }
+}
diff --git a/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.css b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.css
new file mode 100644
index 0000000..fb0995a
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.css
@@ -0,0 +1,56 @@
+/*
+ * 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 -- Quickhelp Panel -- CSS file
+ */
+
+/*
+ ONOS GUI -- Quick Help Service (layout) -- CSS file
+ */
+
+#quickhelp {
+    top: 100px;
+    z-index: 5000;
+    position: absolute;
+    width: 100%;
+}
+
+#quickhelp div.help {
+    background: black;
+    opacity: 0.8;
+}
+
+#quickhelp div.help table {
+    vertical-align: top;
+    width: 100%;
+}
+
+#quickhelp div.help p {
+    text-align: center;
+    color: white;
+    font-weight: bold;
+}
+
+#quickhelp td.key {
+    color: #add;
+    padding-left: 8px;
+    padding-right: 4px;
+}
+
+#quickhelp td.desc {
+    color: white;
+}
diff --git a/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.html b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.html
new file mode 100644
index 0000000..7d96ebf
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.html
@@ -0,0 +1,72 @@
+<!--
+~ 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="quickhelp" [@quickHelpState]="ks.quickHelpShown">
+    <div class="help" *ngIf="ks.quickHelpShown">
+        <p class="title">{{lionFn("qh_title")}}</p>
+        <!-- TODO: drive this through the keys service keyHandler -->
+        <table class="qhrow">
+            <tr>
+                <td class="key">&#92;</td>
+                <td class="desc">{{lionFn("qh_hint_show_hide_qh")}}</td>
+            </tr>
+            <tr>
+                <td class="key">/</td>
+                <td class="desc">{{lionFn("qh_hint_show_hide_qh")}}</td>
+            </tr>
+            <tr>
+                <td class="key">Esc</td>
+                <td class="desc">{{lionFn("qh_hint_esc")}}</td>
+            </tr>
+            <tr>
+                <td class="key">T</td>
+                <td class="desc">{{lionFn("qh_hint_t")}}</td>
+            </tr>
+        </table>
+        <hr class="qhrowsep">
+        <table class="qhrow">
+            <tr *ngFor="let vk of viewKeys; i as index">
+                <ng-container *ngFor="let vkrow of vk">
+                    <td class="key">{{vkrow.keystroke}}</td>
+                    <td class="desc">{{vkrow.text}}</td>
+                </ng-container>
+            </tr>
+        </table>
+        <hr class="qhrowsep">
+        <div class="qhrow">
+            <table class="qh-r4-c0">
+                <tr>
+                    <td class="key">{{lionFnTopo("click")}}</td>
+                    <td class="desc">{{lionFnTopo("qh_gest_click")}}</td>
+                </tr>
+                <tr>
+                    <td class="key">{{lionFnTopo("shift_click")}}</td>
+                    <td class="desc">{{lionFnTopo("qh_gest_shift_click")}}</td>
+                </tr>
+                <tr>
+                    <td class="key">{{lionFnTopo("drag")}}</td>
+                    <td class="desc">{{lionFnTopo("qh_gest_drag")}}</td>
+                </tr>
+                <tr>
+                    <td class="key">{{lionFnTopo("cmd_scroll")}}</td>
+                    <td class="desc">{{lionFnTopo("qh_gest_cmd_scroll")}}</td>
+                </tr>
+                <tr>
+                    <td class="key" y="48">{{lionFnTopo("drag")}}</td>
+                    <td class="desc" y="48" x="74.84375">{{lionFnTopo("qh_gest_cmd_drag")}}</tr>
+            </table>
+        </div>
+    </div>
+</div>
diff --git a/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.spec.ts b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.spec.ts
new file mode 100644
index 0000000..5dfb73c
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.spec.ts
@@ -0,0 +1,92 @@
+/*
+ * 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 { QuickhelpComponent } from './quickhelp.component';
+import {LogService} from '../../log.service';
+import {ConsoleLoggerService} from '../../consolelogger.service';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {FnService} from '../../util/fn.service';
+import {LionService} from '../../util/lion.service';
+import {KeysService} from '../../util/keys.service';
+
+class MockFnService {}
+
+class MockKeysService {
+    keyHandler: {
+        viewKeys: any[],
+        globalKeys: any[]
+    };
+
+    mockViewKeys: Object[];
+    constructor() {
+        this.mockViewKeys = [];
+        this.keyHandler = {
+            viewKeys: this.mockViewKeys,
+            globalKeys: this.mockViewKeys
+        };
+    }
+}
+
+/**
+ * ONOS GUI -- Layer -- Quickhelp Component - Unit Tests
+ */
+describe('QuickhelpComponent', () => {
+    let log: LogService;
+    let component: QuickhelpComponent;
+    let fixture: ComponentFixture<QuickhelpComponent>;
+    const bundleObj = {
+        'core.fw.QuickHelp': {
+            test: 'test1',
+            tt_help: 'Help!'
+        }
+    };
+    const mockLion = (key) =>  {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ QuickhelpComponent ],
+            providers: [
+                { provide: LogService, useValue: log },
+                { provide: FnService, useClass: MockFnService },
+                { provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: KeysService, useClass: MockKeysService }
+            ]
+        })
+        .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(QuickhelpComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.ts b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.ts
new file mode 100644
index 0000000..ca2408c
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/quickhelp/quickhelp.component.ts
@@ -0,0 +1,109 @@
+/*
+ * 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, OnInit} from '@angular/core';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+import {LogService} from '../../log.service';
+import {FnService} from '../../util/fn.service';
+import {KeysService} from '../../util/keys.service';
+import {LionService} from '../../util/lion.service';
+
+export interface KeyEntry {
+    keystroke: string;
+    text: string;
+}
+
+@Component({
+    selector: 'onos-quickhelp',
+    templateUrl: './quickhelp.component.html',
+    styleUrls: ['./quickhelp.component.css'],
+    animations: [
+        trigger('quickHelpState', [
+            state('true', style({
+                opacity: '1.0',
+            })),
+            state('false', style({
+                opacity: '0.0',
+            })),
+            transition('0 => 1', animate('500ms ease-in')),
+            transition('1 => 0', animate('500ms ease-out'))
+        ])
+    ]
+})
+export class QuickhelpComponent implements OnInit {
+    lionFn; // Function
+    lionFnTopo; // Function
+
+    dialogKeys: Object;
+    globalKeys: Object[];
+    maskedKeys: Object;
+    viewGestures: Object;
+    viewKeys: KeyEntry[][];
+
+    private static extractKeyEntry(viewKeyObj: Object, log: LogService): KeyEntry {
+        const subParts = (<any>Object).values(viewKeyObj[1]);
+        return <KeyEntry>{
+            keystroke: <string>viewKeyObj[0],
+            text: <string>subParts[1]
+        };
+    }
+
+    constructor(
+        private log: LogService,
+        private fs: FnService,
+        public ks: KeysService,
+        private lion: LionService
+    ) {
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lionFnTopo = this.dummyLion;
+            this.lion.loadCbs.set('quickhelp', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+        this.globalKeys = [];
+        this.viewKeys = [[], [], [], [], [], [], [], [], []];
+
+        this.log.debug('QuickhelpComponent constructed');
+    }
+
+    ngOnInit(): void {
+        (<any>Object).entries(this.ks.keyHandler.viewKeys)
+            .filter((vk) => vk[0] !== '_helpFormat' && vk[0] !== '9' && vk[0] !== 'esc')
+            .forEach((vk, idx) => {
+                const ke = QuickhelpComponent.extractKeyEntry(vk, this.log);
+                this.viewKeys[Math.floor(idx / 3)][idx % 3] = ke;
+            });
+        this.log.debug('QuickhelpComponent initialized');
+        this.log.debug('view keys retrieved', this.ks.keyHandler.globalKeys);
+    }
+
+
+    /**
+     * Read the LION bundle for Toolbar and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.fw.QuickHelp');
+        this.lionFnTopo = this.lion.bundle('core.view.Topo');
+    }
+
+    /**
+    * A dummy implementation of the lionFn until the response is received and the LION
+    * bundle is received from the WebSocket
+    */
+    dummyLion(key: string): string {
+        return '%' + key + '%';
+    }
+}
diff --git a/web/gui2-fw-lib/lib/layer/quickhelp/test/uberlion_english_sample.json b/web/gui2-fw-lib/lib/layer/quickhelp/test/uberlion_english_sample.json
new file mode 100644
index 0000000..7ff843e
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/quickhelp/test/uberlion_english_sample.json
@@ -0,0 +1,288 @@
+{
+  "event": "uberlion",
+  "payload": {
+    "lion": {
+      "core.fw.Mast": {
+        "confirm_refresh_title": "Confirm GUI Refresh",
+        "logout": "Logout",
+        "tt_help": "Show help page for current view",
+        "ui_ok_to_update": "Press OK to update the GUI.",
+        "uicomp_added": "New GUI components were added.",
+        "uicomp_removed": "Some GUI components were removed.",
+        "unknown_user": "(no one)"
+      },
+      "core.fw.Nav": {
+        "cat_network": "Network",
+        "cat_other": "Other",
+        "cat_platform": "Platform",
+        "nav_item_app": "Applications",
+        "nav_item_cluster": "Cluster Nodes",
+        "nav_item_device": "Devices",
+        "nav_item_host": "Hosts",
+        "nav_item_intent": "Intents",
+        "nav_item_link": "Links",
+        "nav_item_partition": "Partitions",
+        "nav_item_processor": "Packet Processors",
+        "nav_item_settings": "Settings",
+        "nav_item_topo": "Topology",
+        "nav_item_topo2": "Topology 2",
+        "nav_item_tunnel": "Tunnels"
+      },
+      "core.fw.QuickHelp": {
+        "qh_hint_close_detail": "Close the details panel",
+        "qh_hint_esc": "Dismiss dialog or cancel selections",
+        "qh_hint_show_hide_qh": "Show / hide Quick Help",
+        "qh_hint_t": "Toggle theme",
+        "qh_title": "Quick Help"
+      },
+      "core.view.App": {
+        "activate": "Activate",
+        "app_id": "App ID",
+        "category": "Category",
+        "click_row": "click row",
+        "deactivate": "Deactivate",
+        "dlg_confirm_action": "Confirm Action",
+        "dlg_warn_deactivate": "Deactivating or uninstalling this component can have serious negative consequences!",
+        "dlg_warn_own_risk": "** DO SO AT YOUR OWN RISK **",
+        "dp_features": "Features",
+        "dp_permissions": "Permissions",
+        "dp_required_apps": "Required Apps",
+        "icon": "Icon",
+        "nav_item_app": "Applications",
+        "origin": "Origin",
+        "qh_hint_click_row": "Select / deselect application",
+        "qh_hint_close_detail": "Close the details panel",
+        "qh_hint_esc": "Deselect application",
+        "qh_hint_scroll_down": "See more applications",
+        "role": "Role",
+        "scroll_down": "scroll down",
+        "state": "State",
+        "title": "Title",
+        "title_apps": "Applications",
+        "total": "Total",
+        "tt_ctl_activate": "Activate selected application",
+        "tt_ctl_auto_refresh": "Toggle auto refresh",
+        "tt_ctl_deactivate": "Deactivate selected application",
+        "tt_ctl_download": "Download selected application (.oar file)",
+        "tt_ctl_uninstall": "Uninstall selected application",
+        "tt_ctl_upload": "Upload an application (.oar file)",
+        "uninstall": "Uninstall",
+        "version": "Version"
+      },
+      "core.view.Cluster": {
+        "active": "Active",
+        "chassis_id": "Chassis ID",
+        "click": "click",
+        "devices": "Devices",
+        "hw_version": "H/W Version",
+        "ip_address": "IP Address",
+        "last_updated": "Last Updated",
+        "nav_item_cluster": "Cluster Nodes",
+        "node_id": "Node ID",
+        "protocol": "Protocol",
+        "qh_hint_click": "Select a row to show cluster node details",
+        "qh_hint_close_detail": "Close the details panel",
+        "qh_hint_scroll_down": "See available cluster nodes",
+        "scroll_down": "scroll down",
+        "serial_number": "Serial #",
+        "started": "Started",
+        "sw_version": "S/W Version",
+        "tcp_port": "TCP Port",
+        "title_cluster_nodes": "Cluster Nodes",
+        "total": "Total",
+        "type": "Type",
+        "uri": "URI",
+        "vendor": "Vendor"
+      },
+      "core.view.Topo": {
+        "active": "active",
+        "added": "added",
+        "btn_show_view_device": "Show Device View",
+        "btn_show_view_flow": "Show Flow View for this Device",
+        "btn_show_view_group": "Show Group View for this Device",
+        "btn_show_view_meter": "Show Meter View for this Device",
+        "btn_show_view_port": "Show Port View for this Device",
+        "click": "click",
+        "close": "Close",
+        "cmd_drag": "cmd-drag",
+        "cmd_scroll": "cmd-scroll",
+        "device": "Device",
+        "devices": "Devices",
+        "direct": "direct",
+        "disable": "Disable",
+        "drag": "drag",
+        "edge": "edge",
+        "enable": "Enable",
+        "expected": "expected",
+        "fl_background_map": "background map",
+        "fl_bad_links": "Bad Links",
+        "fl_device_labels_hide": "Hide device labels",
+        "fl_device_labels_show_friendly": "Show friendly device labels",
+        "fl_device_labels_show_id": "Show device ID labels",
+        "fl_eq_masters": "Equalizing master roles",
+        "fl_grid_display_1000": "Show XY grid",
+        "fl_grid_display_both": "Show both grids",
+        "fl_grid_display_geo": "Show Geo grid",
+        "fl_grid_display_hide": "Hide grid",
+        "fl_host_labels_hide": "Hide host labels",
+        "fl_host_labels_show_friendly": "Show friendly host labels",
+        "fl_host_labels_show_ip": "Show host IP addresses",
+        "fl_host_labels_show_mac": "Show host MAC addresses",
+        "fl_layer_all": "All Layers Shown",
+        "fl_layer_opt": "Optical Layer Shown",
+        "fl_layer_pkt": "Packet Layer Shown",
+        "fl_monitoring_canceled": "Monitoring Canceled",
+        "fl_normal_view": "Normal View",
+        "fl_oblique_view": "Oblique View",
+        "fl_offline_devices": "Offline Devices",
+        "fl_pan_zoom_reset": "Pan and zoom reset",
+        "fl_panel_details": "Details Panel",
+        "fl_panel_instances": "Instances Panel",
+        "fl_panel_summary": "Summary Panel",
+        "fl_pinned_floating_nodes": "Pinned floating nodes",
+        "fl_port_highlighting": "Port Highlighting",
+        "fl_reset_node_locations": "Reset Node Locations",
+        "fl_selecting_intent": "Selecting Intent",
+        "fl_sprite_layer": "sprite layer",
+        "fl_unpinned_floating_nodes": "Unpinned floating nodes",
+        "flows": "Flows",
+        "grid_x": "Grid X",
+        "grid_y": "Grid Y",
+        "hidden": "Hidden",
+        "hide": "Hide",
+        "host": "Host",
+        "hosts": "Hosts",
+        "hw_version": "H/W Version",
+        "inactive": "inactive",
+        "indirect": "indirect",
+        "intent": "Intent",
+        "intents": "Intents",
+        "ip": "IP",
+        "latitude": "Latitude",
+        "links": "Links",
+        "longitude": "Longitude",
+        "lp_label_a2b": "A to B",
+        "lp_label_a_friendly": "A name",
+        "lp_label_a_id": "A id",
+        "lp_label_a_port": "A port",
+        "lp_label_a_type": "A type",
+        "lp_label_b2a": "B to A",
+        "lp_label_b_friendly": "B name",
+        "lp_label_b_id": "B id",
+        "lp_label_b_port": "B port",
+        "lp_label_b_type": "B type",
+        "lp_label_friendly": "Friendly",
+        "lp_value_no_link": "[no link]",
+        "mac": "MAC",
+        "nav_item_topo": "Topology",
+        "no_devices_are_connected": "No Devices Are Connected",
+        "not_expected": "not expected",
+        "ok": "OK",
+        "optical": "optical",
+        "ov_tt_none": "No Overlay",
+        "ov_tt_protected_intents": "Protected Intents Overlay",
+        "ov_tt_traffic": "Traffic Overlay",
+        "ports": "Ports",
+        "protocol": "Protocol",
+        "purged": "purged",
+        "qh_gest_click": "Select the item and show details",
+        "qh_gest_cmd_drag": "Pan",
+        "qh_gest_cmd_scroll": "Zoom in / out",
+        "qh_gest_drag": "Reposition (and pin) device / host",
+        "qh_gest_shift_click": "Toggle selection state",
+        "qh_hint_close_detail": "Close the details panel",
+        "resubmitted": "resubmitted",
+        "select": "Select",
+        "serial_number": "Serial #",
+        "shift_click": "shift-click",
+        "show": "Show",
+        "sw_version": "S/W Version",
+        "tbtt_bad_links": "Show bad links",
+        "tbtt_cyc_dev_labs": "Cycle device labels",
+        "tbtt_cyc_grid_display": "Cycle grid display",
+        "tbtt_cyc_host_labs": "Cycle host labels",
+        "tbtt_cyc_layers": "Cycle node layers",
+        "tbtt_eq_master": "Equalize mastership roles",
+        "tbtt_reset_loc": "Reset node locations",
+        "tbtt_reset_zoom": "Reset pan / zoom",
+        "tbtt_sel_map": "Select background geo map",
+        "tbtt_tog_host": "Toggle host visibility",
+        "tbtt_tog_instances": "Toggle ONOS instances panel",
+        "tbtt_tog_map": "Toggle background geo map",
+        "tbtt_tog_oblique": "Toggle oblique view (experimental)",
+        "tbtt_tog_offline": "Toggle offline visibility",
+        "tbtt_tog_porthi": "Toggle port highlighting",
+        "tbtt_tog_sprite": "Toggle sprite layer",
+        "tbtt_tog_summary": "Toggle ONOS summary panel",
+        "tbtt_tog_toolbar": "Toggle Toolbar",
+        "tbtt_tog_use_detail": "Disable / enable details panel",
+        "tbtt_unpin_node": "Unpin node (hover mouse over)",
+        "title_edge_link": "Edge Link",
+        "title_infra_link": "Infrastructure Link",
+        "title_panel_summary": "ONOS Summary",
+        "title_select_map": "Select Map",
+        "title_selected_items": "Selected Items",
+        "topology_sccs": "Topology SCCs",
+        "tr_btn_cancel_monitoring": "Cancel traffic monitoring",
+        "tr_btn_create_h2h_flow": "Create Host-to-Host Flow",
+        "tr_btn_create_msrc_flow": "Create Multi-Source Flow",
+        "tr_btn_monitor_all": "Monitor all traffic",
+        "tr_btn_monitor_sel_intent": "Monitor traffic of selected intent",
+        "tr_btn_show_all_rel_intents": "Show all related intents",
+        "tr_btn_show_dev_link_flows": "Show device link flows",
+        "tr_btn_show_device_flows": "Show Device Flows",
+        "tr_btn_show_next_rel_intent": "Show next related intent",
+        "tr_btn_show_prev_rel_intent": "Show previous related intent",
+        "tr_btn_show_related_traffic": "Show Related Traffic",
+        "tr_fl_dev_flows": "Device Flows",
+        "tr_fl_fstats_bytes": "Flow Stats (bytes)",
+        "tr_fl_h2h_flow_added": "Host-to-Host flow added",
+        "tr_fl_multisrc_flow": "Multi-Source Flow",
+        "tr_fl_next_rel_int": "Next related intent",
+        "tr_fl_prev_rel_int": "Previous related intent",
+        "tr_fl_pstats_bits": "Port Stats (bits / second)",
+        "tr_fl_pstats_pkts": "Port Stats (packets / second)",
+        "tr_fl_rel_paths": "Related Paths",
+        "tr_fl_traf_on_path": "Traffic on Selected Path",
+        "tunnel": "tunnel",
+        "tunnels": "Tunnels",
+        "uri": "URI",
+        "vendor": "Vendor",
+        "version": "Version",
+        "virtual": "virtual",
+        "visible": "Visible",
+        "vlan": "VLAN",
+        "vlan_none": "None",
+        "withdrawn": "withdrawn"
+      },
+      "core.view.Flow": {
+        "appId": "App ID",
+        "appName": "App Name",
+        "bytes": "Bytes",
+        "duration": "Duration ",
+        "flowId": "Flow ID",
+        "groupId": "Group ID",
+        "hardTimeout": "Hard Timeout",
+        "idleTimeout": "Idle Timeout",
+        "nav_item_flow": "Flows",
+        "packets": "Packets",
+        "permanent": "Permanent",
+        "priority": "Flow Priority ",
+        "selector": "Selector",
+        "state": "State",
+        "tableName": "Table Name ",
+        "title_flows": "Flows for Device ",
+        "total": "Total",
+        "treatment": "Treatment",
+        "tt_ctl_show_device": "Show device table",
+        "tt_ctl_show_group": "Show group view for this device",
+        "tt_ctl_show_meter": "Show meter view for selected device",
+        "tt_ctl_show_pipeconf": "Show pipeconf view for selected device",
+        "tt_ctl_show_port": "Show port view for this device",
+        "tt_ctl_switcth_brief": "Switch to brief view",
+        "tt_ctl_switcth_detailed": "Switch to detailed view"
+      }
+    },
+    "locale": "en_IE"
+  }
+}
diff --git a/web/gui2-fw-lib/lib/layer/veil/veil.component.css b/web/gui2-fw-lib/lib/layer/veil/veil.component.css
new file mode 100644
index 0000000..b688ae7
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/veil/veil.component.css
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Veil Service (layout) -- CSS file
+ */
+
+#veil {
+    z-index: 5000;
+    display: block;
+    position: absolute;
+    top: 0;
+    left: 0;
+    padding: 60px;
+}
+
+#veil p {
+    display: block;
+    text-align: left;
+    font-size: 14pt;
+    font-style: italic;
+}
diff --git a/web/gui2-fw-lib/lib/layer/veil/veil.component.html b/web/gui2-fw-lib/lib/layer/veil/veil.component.html
new file mode 100644
index 0000000..69410c5
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/veil/veil.component.html
@@ -0,0 +1,25 @@
+<!--
+~ 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="veil" *ngIf="enabled">
+    <p *ngFor="let msg of messages">{{ msg }}</p>
+    <svg [attr.width]="fs.windowSize().width" [attr.height]="fs.windowSize().height">
+        <use [attr.width]="birdDim" [attr.height]="birdDim" class="glyph"
+             style="opacity: 0.2;"
+             xlink:href = "#bird" [attr.transform]="trans"/>
+
+    </svg>
+</div>
diff --git a/web/gui2-fw-lib/lib/layer/veil/veil.component.spec.ts b/web/gui2-fw-lib/lib/layer/veil/veil.component.spec.ts
new file mode 100644
index 0000000..72123dd
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/veil/veil.component.spec.ts
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Layer -- Veil Service - Unit Tests
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+
+import { VeilComponent } from './veil.component';
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { FnService } from '../../util/fn.service';
+import { LogService } from '../../log.service';
+import { GlyphService } from '../../svg/glyph.service';
+import { of } from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+class MockGlyphService {}
+
+describe('VeilComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({});
+        windowMock = <any>{
+            location: <any> {
+                hostname: 'foo'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+
+        TestBed.configureTestingModule({
+            declarations: [ VeilComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                { provide: GlyphService, useClass: MockGlyphService },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        });
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    it('should create', () => {
+        const fixture = TestBed.createComponent(VeilComponent);
+        const component = fixture.componentInstance;
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2-fw-lib/lib/layer/veil/veil.component.theme.css b/web/gui2-fw-lib/lib/layer/veil/veil.component.theme.css
new file mode 100644
index 0000000..2939b9f
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/veil/veil.component.theme.css
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Veil Service (theme) -- CSS file
+ */
+
+.light, #veil {
+    background-color: rgba(0,0,0,0.75);
+}
+.dark, #veil {
+    background-color: rgba(64,64,64,0.75);
+}
+
+#veil p {
+    color: #ddd;
+}
+
+#veil svg .glyph {
+    fill: #222;
+}
diff --git a/web/gui2-fw-lib/lib/layer/veil/veil.component.ts b/web/gui2-fw-lib/lib/layer/veil/veil.component.ts
new file mode 100644
index 0000000..b93d950
--- /dev/null
+++ b/web/gui2-fw-lib/lib/layer/veil/veil.component.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 { Component, OnInit } from '@angular/core';
+import { FnService } from '../../util/fn.service';
+import { GlyphService } from '../../svg/glyph.service';
+import { LogService } from '../../log.service';
+import { SvgUtilService } from '../../svg/svgutil.service';
+import { WebSocketService } from '../../remote/websocket.service';
+
+const BIRD = 'bird';
+
+/**
+ * ONOS GUI -- Layer -- Veil Component
+ *
+ * Provides a mechanism to display an overlaying div with information.
+ * Used mainly for web socket connection interruption.
+ *
+ * It can be added to an component's template as follows:
+ *     <onos-veil #veil></onos-veil>
+ *     <p (click)="veil.show(['t1','t2','t3'])">Test Veil</p>
+ */
+@Component({
+  selector: 'onos-veil',
+  templateUrl: './veil.component.html',
+  styleUrls: ['./veil.component.css', './veil.component.theme.css']
+})
+export class VeilComponent implements OnInit {
+    ww: number;
+    wh: number;
+    birdSvg: string;
+    birdDim: number;
+    enabled: boolean = false;
+    trans: string;
+    messages: string[] = [];
+    veilStyle: string;
+
+    constructor(
+        public fs: FnService,
+        private gs: GlyphService,
+        private log: LogService,
+        private sus: SvgUtilService,
+        private wss: WebSocketService
+    ) {
+        const wSize = this.fs.windowSize();
+        this.ww = wSize.width;
+        this.wh = wSize.height;
+        const shrink = this.wh * 0.3;
+        this.birdDim = this.wh - shrink;
+        const birdCenter = (this.ww - this.birdDim) / 2;
+        this.trans = this.sus.translate([birdCenter, shrink / 2]);
+
+        this.log.debug('VeilComponent with ' + BIRD + ' constructed');
+    }
+
+    ngOnInit() {
+    }
+
+    // msg should be an array of strings
+    show(msgs: string[]): void {
+        this.messages = msgs;
+        this.enabled = true;
+//        this.ks.enableKeys(false);
+    }
+
+    hide(): void {
+        this.veilStyle = 'display: none';
+//        this.ks.enableKeys(true);
+    }
+
+
+}