Added in panel support - details panels

Change-Id: I2803edd6fe12cb0d97a2d3c45a692ea701786dd2
diff --git a/web/gui2/AngularMigration.md b/web/gui2/AngularMigration.md
index a1cf791..82e4b29 100644
--- a/web/gui2/AngularMigration.md
+++ b/web/gui2/AngularMigration.md
@@ -1,4 +1,4 @@
-#Migrating ONOS GUI
+# Migrating ONOS GUI
 Code written for Angular 1.x can be converted to Angular 5, through a line by line migration process (aka a hard slog)
 
 * It is important to know that Angular 5 code is written in TypeScript and all files end in .ts
@@ -8,6 +8,10 @@
   * See https://webapplog.com/es6/ for a list of things that ES6 brings
 * Each item (Service, Component, Directive, Pipe or Module) gets its own file ending with this type e.g. function.service.ts 
 * Each test file is the name of the item with .spec.ts e.g. function.service.spec.ts
+    * It is considered best practice to put the Unit test of a component or service
+   right next to it in the folder - some new unit tests have been placed in a
+   tests folder, but they will have to come back out from there in to the 
+   individual folders.
 * Modules are used to group together services, components, directives etc
 * When starting any new component use the `ng generate ..` This will create the associated test too
 
@@ -53,7 +57,7 @@
 If documentation has been created (using `npm run compodoc`) this module can be inspected at
 [OnosModule](./documentation/modules/OnosModule.html)
 
-#Angular CLI
+# Angular CLI
 The Angular CLI tool has many handy modes to help you along the way.
 From the onos/web/gui folder you should be able to run 
 
@@ -98,7 +102,7 @@
 Similarly a directive might be trying to do DOM manipulation and have a CSS - this 
 should be made in to a component instead (see IconComponent)
 
-###How do I know whether a Service or Directive should be made a Component in this new GUI?
+### How do I know whether a Service or Directive should be made a Component in this new GUI?
 The general rule to follow is _"if a service in the old GUI has an associated CSS 
 file or two then is should be a component in the new GUI"_. 
 
@@ -115,6 +119,8 @@
 ### Do not inject components in to services
 Components are graphical elements and should not be injected in to Services. 
 Services should be injected in to components, but not the other way round.
+Components can be added in to other components by putting the selector of 
+the child component e.g. <onos-icon> in to the html template of the parent.
 
 Take for instance the WebSocketService - this should remain a service, but I want 
 to display the LoadingComponent while it's waiting and the VeilComponent if it
@@ -259,6 +265,11 @@
 * fw/remote/WebSocketService - fully implemented with Unit tests
 * fw/widget/TableBase - previously the TableBuilderService this has now been changed
 to a plain interface and class - any table views should extend this
+* fw/widget/PanelBase - previously the PanelService - this is an abstract base class
+  that both dialogs and details panels are based off
+* fw/widget/DetailsPanelBase - previously the DetailsPanelService - this has functions 
+  for accessing the WebSocket service for details. If extends PanelBase and is the 
+  base for AppsDetailsComponent and DeviceDetailsComponent
 
 
 # Devices View
@@ -274,7 +285,11 @@
 For CSS the old device view CSS is included and a link is made across to the
 common table CSS
 
-#Apps View
+The Details Panel is made visible when a row is selected - it is a component, and is
+embedded in to the repeated row. There are base classes for common details panel 
+behaviour 
+
+# Apps View
 This is a Component too, again extending TableBase. Apps view has much more functionality 
 though because it has controls for upload and download of applications.
 
diff --git a/web/gui2/BUCK b/web/gui2/BUCK
index 94c492e..8bf9787 100644
--- a/web/gui2/BUCK
+++ b/web/gui2/BUCK
@@ -105,10 +105,15 @@
     bash =
         'export PATH=$(location :node-bin-' + NODE_VERSION + ')/bin:$PATH; '
         + 'cd $(location :onos-web-gui2-build)/../../onos-web-gui2-build__srcs;'
-        + 'pwd > "$OUT";'
-        + 'npm5 -v >> "$OUT";'
-        + 'ng -v >> "$OUT";'
-        + 'ng lint >> "$OUT"',
+#         + 'pwd > "$OUT";'
+#         + 'npm5 -v >> "$OUT";'
+#         + 'ng -v >> "$OUT";'
+        + 'ng lint >> "$OUT" 2>&1 || '
+        + 'if [ $? -eq 0 ]; then echo "Successfully ran lint";'
+        + 'else '
+        + ' cat $OUT >&2;'
+        + ' exit 1;'
+        + 'fi;',
     out = 'onos-web-gui2-lint-log.txt',
 )
 
diff --git a/web/gui2/README.md b/web/gui2/README.md
index 80976ea..9883467 100644
--- a/web/gui2/README.md
+++ b/web/gui2/README.md
@@ -13,6 +13,12 @@
 ```
 and the gui will be accessible at [http://localhost:8181/onos/ui2](http://localhost:8181/onos/ui2)
 
+As usual with ONOS if you want to run it in a different language set the __ONOS_LOCALE__ environment variable
+to the locale you want before starting onos. e.g.
+```
+ONOS_LOCALE=fr_FR onos-buck run onos-local
+```
+
 # Development
 There are 2 ways to go about development - 
 1. rebuild the code and rerun through BUCK (much like can be done with any ordinary ONOS app) 
@@ -62,7 +68,12 @@
 
 Press Ctrl-Shift-I in Chrome and Firefox to bring up the developer tools and the browser console.
 
-There are certain extra debugging can be turned on by adding the parameter 'debug' For example to turn extra logging for WebSockets add on __?debug=txrx__ 
+There are certain extra debugging can be turned on by adding the parameter 'debug' 
+For example to turn extra logging for WebSockets add on __?debug=txrx__
+
+On the Apps view - icons will appear to be missing - this is because they use a relative path to
+source the image, and this path is not available in this 'ng serve' mode. The icons work fine in the
+mode where it's run inside ONOS. 
 
 ## Code scaffolding
 
diff --git a/web/gui2/angular.json b/web/gui2/angular.json
index 8341e91..3563d9e 100644
--- a/web/gui2/angular.json
+++ b/web/gui2/angular.json
@@ -63,7 +63,7 @@
         "test": {
           "builder": "@angular-devkit/build-angular:karma",
           "options": {
-            "main": "src/main/webapp/tests/test.ts",
+            "main": "src/main/webapp/app/test.ts",
             "karmaConfig": "./karma.conf.js",
             "polyfills": "src/main/webapp/polyfills.ts",
             "tsConfig": "src/main/webapp/tsconfig.spec.json",
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index eadeb8c..2c1ad19 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -1334,6 +1334,1109 @@
         "tslib": "1.9.0"
       }
     },
+    "@compodoc/compodoc": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.3.tgz",
+      "integrity": "sha512-MXo8O5Ar5HB0OSayGQknUWCo/zLglz348tDkoOii020y+bEBPRTRcwCjKaORJZG1Nw9nmStGyVxPlf1cxW6w8A==",
+      "dev": true,
+      "requires": {
+        "@compodoc/ngd-transformer": "2.0.0",
+        "chalk": "2.4.1",
+        "cheerio": "1.0.0-rc.2",
+        "chokidar": "2.0.4",
+        "colors": "1.3.0",
+        "commander": "2.15.1",
+        "cosmiconfig": "5.0.5",
+        "fancy-log": "1.3.2",
+        "findit2": "2.2.3",
+        "fs-extra": "6.0.1",
+        "glob": "7.1.2",
+        "handlebars": "4.0.10",
+        "html-entities": "1.2.1",
+        "json5": "1.0.1",
+        "live-server": "1.1.0",
+        "lodash": "4.17.10",
+        "lunr": "2.2.0",
+        "marked": "0.3.19",
+        "os-name": "2.0.1",
+        "traverse": "0.6.6",
+        "ts-simple-ast": "11.2.0",
+        "uuid": "3.2.1"
+      },
+      "dependencies": {
+        "anymatch": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+          "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+          "dev": true,
+          "requires": {
+            "micromatch": "3.1.10",
+            "normalize-path": "2.1.1"
+          }
+        },
+        "arr-diff": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+          "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+          "dev": true
+        },
+        "array-unique": {
+          "version": "0.3.2",
+          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+          "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+          "dev": true
+        },
+        "async": {
+          "version": "1.5.2",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+          "dev": true
+        },
+        "braces": {
+          "version": "2.3.2",
+          "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+          "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+          "dev": true,
+          "requires": {
+            "arr-flatten": "1.1.0",
+            "array-unique": "0.3.2",
+            "extend-shallow": "2.0.1",
+            "fill-range": "4.0.0",
+            "isobject": "3.0.1",
+            "repeat-element": "1.1.2",
+            "snapdragon": "0.8.2",
+            "snapdragon-node": "2.1.1",
+            "split-string": "3.1.0",
+            "to-regex": "3.0.2"
+          },
+          "dependencies": {
+            "extend-shallow": {
+              "version": "2.0.1",
+              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+              "dev": true,
+              "requires": {
+                "is-extendable": "0.1.1"
+              }
+            }
+          }
+        },
+        "camelcase": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+          "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
+          "dev": true,
+          "optional": true
+        },
+        "chalk": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+          "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "3.2.1",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "5.4.0"
+          }
+        },
+        "chokidar": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
+          "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
+          "dev": true,
+          "requires": {
+            "anymatch": "2.0.0",
+            "async-each": "1.0.1",
+            "braces": "2.3.2",
+            "fsevents": "1.2.4",
+            "glob-parent": "3.1.0",
+            "inherits": "2.0.3",
+            "is-binary-path": "1.0.1",
+            "is-glob": "4.0.0",
+            "lodash.debounce": "4.0.8",
+            "normalize-path": "2.1.1",
+            "path-is-absolute": "1.0.1",
+            "readdirp": "2.1.0",
+            "upath": "1.0.5"
+          }
+        },
+        "cliui": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+          "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "center-align": "0.1.3",
+            "right-align": "0.1.3",
+            "wordwrap": "0.0.2"
+          }
+        },
+        "colors": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz",
+          "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==",
+          "dev": true
+        },
+        "cosmiconfig": {
+          "version": "5.0.5",
+          "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz",
+          "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==",
+          "dev": true,
+          "requires": {
+            "is-directory": "0.3.1",
+            "js-yaml": "3.12.0",
+            "parse-json": "4.0.0"
+          }
+        },
+        "esprima": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+          "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+          "dev": true
+        },
+        "expand-brackets": {
+          "version": "2.1.4",
+          "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+          "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+          "dev": true,
+          "requires": {
+            "debug": "2.6.9",
+            "define-property": "0.2.5",
+            "extend-shallow": "2.0.1",
+            "posix-character-classes": "0.1.1",
+            "regex-not": "1.0.2",
+            "snapdragon": "0.8.2",
+            "to-regex": "3.0.2"
+          },
+          "dependencies": {
+            "define-property": {
+              "version": "0.2.5",
+              "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+              "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+              "dev": true,
+              "requires": {
+                "is-descriptor": "0.1.6"
+              }
+            },
+            "extend-shallow": {
+              "version": "2.0.1",
+              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+              "dev": true,
+              "requires": {
+                "is-extendable": "0.1.1"
+              }
+            },
+            "is-accessor-descriptor": {
+              "version": "0.1.6",
+              "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+              "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+              "dev": true,
+              "requires": {
+                "kind-of": "3.2.2"
+              },
+              "dependencies": {
+                "kind-of": {
+                  "version": "3.2.2",
+                  "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+                  "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+                  "dev": true,
+                  "requires": {
+                    "is-buffer": "1.1.6"
+                  }
+                }
+              }
+            },
+            "is-data-descriptor": {
+              "version": "0.1.4",
+              "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+              "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+              "dev": true,
+              "requires": {
+                "kind-of": "3.2.2"
+              },
+              "dependencies": {
+                "kind-of": {
+                  "version": "3.2.2",
+                  "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+                  "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+                  "dev": true,
+                  "requires": {
+                    "is-buffer": "1.1.6"
+                  }
+                }
+              }
+            },
+            "is-descriptor": {
+              "version": "0.1.6",
+              "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+              "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+              "dev": true,
+              "requires": {
+                "is-accessor-descriptor": "0.1.6",
+                "is-data-descriptor": "0.1.4",
+                "kind-of": "5.1.0"
+              }
+            },
+            "kind-of": {
+              "version": "5.1.0",
+              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+              "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+              "dev": true
+            }
+          }
+        },
+        "extglob": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+          "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+          "dev": true,
+          "requires": {
+            "array-unique": "0.3.2",
+            "define-property": "1.0.0",
+            "expand-brackets": "2.1.4",
+            "extend-shallow": "2.0.1",
+            "fragment-cache": "0.2.1",
+            "regex-not": "1.0.2",
+            "snapdragon": "0.8.2",
+            "to-regex": "3.0.2"
+          },
+          "dependencies": {
+            "define-property": {
+              "version": "1.0.0",
+              "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+              "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+              "dev": true,
+              "requires": {
+                "is-descriptor": "1.0.2"
+              }
+            },
+            "extend-shallow": {
+              "version": "2.0.1",
+              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+              "dev": true,
+              "requires": {
+                "is-extendable": "0.1.1"
+              }
+            }
+          }
+        },
+        "fill-range": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+          "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+          "dev": true,
+          "requires": {
+            "extend-shallow": "2.0.1",
+            "is-number": "3.0.0",
+            "repeat-string": "1.6.1",
+            "to-regex-range": "2.1.1"
+          },
+          "dependencies": {
+            "extend-shallow": {
+              "version": "2.0.1",
+              "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+              "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+              "dev": true,
+              "requires": {
+                "is-extendable": "0.1.1"
+              }
+            }
+          }
+        },
+        "fsevents": {
+          "version": "1.2.4",
+          "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
+          "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "nan": "2.10.0",
+            "node-pre-gyp": "0.10.0"
+          },
+          "dependencies": {
+            "abbrev": {
+              "version": "1.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "ansi-regex": {
+              "version": "2.1.1",
+              "bundled": true,
+              "dev": true
+            },
+            "aproba": {
+              "version": "1.2.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "are-we-there-yet": {
+              "version": "1.1.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "delegates": "1.0.0",
+                "readable-stream": "2.3.6"
+              }
+            },
+            "balanced-match": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true
+            },
+            "brace-expansion": {
+              "version": "1.1.11",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "balanced-match": "1.0.0",
+                "concat-map": "0.0.1"
+              }
+            },
+            "chownr": {
+              "version": "1.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "code-point-at": {
+              "version": "1.1.0",
+              "bundled": true,
+              "dev": true
+            },
+            "concat-map": {
+              "version": "0.0.1",
+              "bundled": true,
+              "dev": true
+            },
+            "console-control-strings": {
+              "version": "1.1.0",
+              "bundled": true,
+              "dev": true
+            },
+            "core-util-is": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "debug": {
+              "version": "2.6.9",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "ms": "2.0.0"
+              }
+            },
+            "deep-extend": {
+              "version": "0.5.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "delegates": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "detect-libc": {
+              "version": "1.0.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "fs-minipass": {
+              "version": "1.2.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minipass": "2.2.4"
+              }
+            },
+            "fs.realpath": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "gauge": {
+              "version": "2.7.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "aproba": "1.2.0",
+                "console-control-strings": "1.1.0",
+                "has-unicode": "2.0.1",
+                "object-assign": "4.1.1",
+                "signal-exit": "3.0.2",
+                "string-width": "1.0.2",
+                "strip-ansi": "3.0.1",
+                "wide-align": "1.1.2"
+              }
+            },
+            "glob": {
+              "version": "7.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "fs.realpath": "1.0.0",
+                "inflight": "1.0.6",
+                "inherits": "2.0.3",
+                "minimatch": "3.0.4",
+                "once": "1.4.0",
+                "path-is-absolute": "1.0.1"
+              }
+            },
+            "has-unicode": {
+              "version": "2.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "iconv-lite": {
+              "version": "0.4.21",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "safer-buffer": "2.1.2"
+              }
+            },
+            "ignore-walk": {
+              "version": "3.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minimatch": "3.0.4"
+              }
+            },
+            "inflight": {
+              "version": "1.0.6",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "once": "1.4.0",
+                "wrappy": "1.0.2"
+              }
+            },
+            "inherits": {
+              "version": "2.0.3",
+              "bundled": true,
+              "dev": true
+            },
+            "ini": {
+              "version": "1.3.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "is-fullwidth-code-point": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "number-is-nan": "1.0.1"
+              }
+            },
+            "isarray": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "minimatch": {
+              "version": "3.0.4",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "brace-expansion": "1.1.11"
+              }
+            },
+            "minimist": {
+              "version": "0.0.8",
+              "bundled": true,
+              "dev": true
+            },
+            "minipass": {
+              "version": "2.2.4",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "safe-buffer": "5.1.1",
+                "yallist": "3.0.2"
+              }
+            },
+            "minizlib": {
+              "version": "1.1.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minipass": "2.2.4"
+              }
+            },
+            "mkdirp": {
+              "version": "0.5.1",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "minimist": "0.0.8"
+              }
+            },
+            "ms": {
+              "version": "2.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "needle": {
+              "version": "2.2.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "debug": "2.6.9",
+                "iconv-lite": "0.4.21",
+                "sax": "1.2.4"
+              }
+            },
+            "node-pre-gyp": {
+              "version": "0.10.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "detect-libc": "1.0.3",
+                "mkdirp": "0.5.1",
+                "needle": "2.2.0",
+                "nopt": "4.0.1",
+                "npm-packlist": "1.1.10",
+                "npmlog": "4.1.2",
+                "rc": "1.2.7",
+                "rimraf": "2.6.2",
+                "semver": "5.5.0",
+                "tar": "4.4.1"
+              }
+            },
+            "nopt": {
+              "version": "4.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "abbrev": "1.1.1",
+                "osenv": "0.1.5"
+              }
+            },
+            "npm-bundled": {
+              "version": "1.0.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "npm-packlist": {
+              "version": "1.1.10",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "ignore-walk": "3.0.1",
+                "npm-bundled": "1.0.3"
+              }
+            },
+            "npmlog": {
+              "version": "4.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "are-we-there-yet": "1.1.4",
+                "console-control-strings": "1.1.0",
+                "gauge": "2.7.4",
+                "set-blocking": "2.0.0"
+              }
+            },
+            "number-is-nan": {
+              "version": "1.0.1",
+              "bundled": true,
+              "dev": true
+            },
+            "object-assign": {
+              "version": "4.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "once": {
+              "version": "1.4.0",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "wrappy": "1.0.2"
+              }
+            },
+            "os-homedir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "os-tmpdir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "osenv": {
+              "version": "0.1.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "os-homedir": "1.0.2",
+                "os-tmpdir": "1.0.2"
+              }
+            },
+            "path-is-absolute": {
+              "version": "1.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "process-nextick-args": {
+              "version": "2.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "rc": {
+              "version": "1.2.7",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "deep-extend": "0.5.1",
+                "ini": "1.3.5",
+                "minimist": "1.2.0",
+                "strip-json-comments": "2.0.1"
+              },
+              "dependencies": {
+                "minimist": {
+                  "version": "1.2.0",
+                  "bundled": true,
+                  "dev": true,
+                  "optional": true
+                }
+              }
+            },
+            "readable-stream": {
+              "version": "2.3.6",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "core-util-is": "1.0.2",
+                "inherits": "2.0.3",
+                "isarray": "1.0.0",
+                "process-nextick-args": "2.0.0",
+                "safe-buffer": "5.1.1",
+                "string_decoder": "1.1.1",
+                "util-deprecate": "1.0.2"
+              }
+            },
+            "rimraf": {
+              "version": "2.6.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "glob": "7.1.2"
+              }
+            },
+            "safe-buffer": {
+              "version": "5.1.1",
+              "bundled": true,
+              "dev": true
+            },
+            "safer-buffer": {
+              "version": "2.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "sax": {
+              "version": "1.2.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "semver": {
+              "version": "5.5.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "set-blocking": {
+              "version": "2.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "signal-exit": {
+              "version": "3.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "string-width": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "code-point-at": "1.1.0",
+                "is-fullwidth-code-point": "1.0.0",
+                "strip-ansi": "3.0.1"
+              }
+            },
+            "string_decoder": {
+              "version": "1.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "safe-buffer": "5.1.1"
+              }
+            },
+            "strip-ansi": {
+              "version": "3.0.1",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "ansi-regex": "2.1.1"
+              }
+            },
+            "strip-json-comments": {
+              "version": "2.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "tar": {
+              "version": "4.4.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "chownr": "1.0.1",
+                "fs-minipass": "1.2.5",
+                "minipass": "2.2.4",
+                "minizlib": "1.1.0",
+                "mkdirp": "0.5.1",
+                "safe-buffer": "5.1.1",
+                "yallist": "3.0.2"
+              }
+            },
+            "util-deprecate": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "wide-align": {
+              "version": "1.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "string-width": "1.0.2"
+              }
+            },
+            "wrappy": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true
+            },
+            "yallist": {
+              "version": "3.0.2",
+              "bundled": true,
+              "dev": true
+            }
+          }
+        },
+        "glob-parent": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+          "dev": true,
+          "requires": {
+            "is-glob": "3.1.0",
+            "path-dirname": "1.0.2"
+          },
+          "dependencies": {
+            "is-glob": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+              "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+              "dev": true,
+              "requires": {
+                "is-extglob": "2.1.1"
+              }
+            }
+          }
+        },
+        "handlebars": {
+          "version": "4.0.10",
+          "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz",
+          "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=",
+          "dev": true,
+          "requires": {
+            "async": "1.5.2",
+            "optimist": "0.6.1",
+            "source-map": "0.4.4",
+            "uglify-js": "2.8.29"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "6.0.2"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "6.0.2"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "1.0.0",
+            "is-data-descriptor": "1.0.0",
+            "kind-of": "6.0.2"
+          }
+        },
+        "is-extglob": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+          "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+          "dev": true
+        },
+        "is-glob": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+          "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "2.1.1"
+          }
+        },
+        "is-number": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+          "dev": true,
+          "requires": {
+            "kind-of": "3.2.2"
+          },
+          "dependencies": {
+            "kind-of": {
+              "version": "3.2.2",
+              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+              "dev": true,
+              "requires": {
+                "is-buffer": "1.1.6"
+              }
+            }
+          }
+        },
+        "isobject": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+          "dev": true
+        },
+        "js-yaml": {
+          "version": "3.12.0",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
+          "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
+          "dev": true,
+          "requires": {
+            "argparse": "1.0.10",
+            "esprima": "4.0.0"
+          }
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "1.2.0"
+          }
+        },
+        "kind-of": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+          "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+          "dev": true
+        },
+        "lodash": {
+          "version": "4.17.10",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+          "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
+          "dev": true
+        },
+        "micromatch": {
+          "version": "3.1.10",
+          "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+          "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+          "dev": true,
+          "requires": {
+            "arr-diff": "4.0.0",
+            "array-unique": "0.3.2",
+            "braces": "2.3.2",
+            "define-property": "2.0.2",
+            "extend-shallow": "3.0.2",
+            "extglob": "2.0.4",
+            "fragment-cache": "0.2.1",
+            "kind-of": "6.0.2",
+            "nanomatch": "1.2.9",
+            "object.pick": "1.3.0",
+            "regex-not": "1.0.2",
+            "snapdragon": "0.8.2",
+            "to-regex": "3.0.2"
+          }
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "1.3.1",
+            "json-parse-better-errors": "1.0.2"
+          }
+        },
+        "source-map": {
+          "version": "0.4.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+          "dev": true,
+          "requires": {
+            "amdefine": "1.0.1"
+          }
+        },
+        "uglify-js": {
+          "version": "2.8.29",
+          "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
+          "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "source-map": "0.5.7",
+            "uglify-to-browserify": "1.0.2",
+            "yargs": "3.10.0"
+          },
+          "dependencies": {
+            "source-map": {
+              "version": "0.5.7",
+              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+              "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "yargs": {
+          "version": "3.10.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+          "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "camelcase": "1.2.1",
+            "cliui": "2.1.0",
+            "decamelize": "1.2.0",
+            "window-size": "0.1.0"
+          }
+        }
+      }
+    },
+    "@compodoc/ngd-core": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@compodoc/ngd-core/-/ngd-core-2.0.0.tgz",
+      "integrity": "sha512-6HpYvXRZBdIYFojWxW5EVNkhYPmblytCve62CNoYBSWfy++vTGH7Ypg2Bhjg2CsqeV8JOVxrPO7JM9M3MgWKEA==",
+      "dev": true,
+      "requires": {
+        "ansi-colors": "1.1.0",
+        "fancy-log": "1.3.2",
+        "typescript": "2.7.2"
+      }
+    },
+    "@compodoc/ngd-transformer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@compodoc/ngd-transformer/-/ngd-transformer-2.0.0.tgz",
+      "integrity": "sha512-9J0KkmuuuvDHxH0oREgrgbqdEFqcltQXIBofeYdIyMKzI3A+pN1Ji4zfi7x1ql0Ax7qQKemp8XWP+cCpP0qY+w==",
+      "dev": true,
+      "requires": {
+        "@compodoc/ngd-core": "2.0.0",
+        "dot": "1.1.2",
+        "fs-extra": "4.0.3",
+        "viz.js": "1.8.2"
+      },
+      "dependencies": {
+        "fs-extra": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+          "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "jsonfile": "4.0.0",
+            "universalify": "0.1.2"
+          }
+        }
+      }
+    },
+    "@dsherret/to-absolute-glob": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
+      "integrity": "sha1-H2R13IvZdM6gei2vOGSzF7HdMyw=",
+      "dev": true,
+      "requires": {
+        "is-absolute": "1.0.0",
+        "is-negated-glob": "1.0.0"
+      }
+    },
     "@ngtools/webpack": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-6.0.0.tgz",
@@ -2485,6 +3588,15 @@
         }
       }
     },
+    "@types/fs-extra": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.3.tgz",
+      "integrity": "sha512-m2QKoRrJnei1s10e1ZfMIa7QcABBVLdQhVUv5+bMRaHhPzxO/IWMmREiags59AqFbI+piWPq/PupTahNS/0kyA==",
+      "dev": true,
+      "requires": {
+        "@types/node": "8.9.5"
+      }
+    },
     "@types/jasmine": {
       "version": "2.8.6",
       "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.6.tgz",
@@ -2608,12 +3720,40 @@
         "repeat-string": "1.6.1"
       }
     },
+    "ambi": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/ambi/-/ambi-2.5.0.tgz",
+      "integrity": "sha1-fI43K+SIkRV+fOoBy2+RQ9H3QiA=",
+      "dev": true,
+      "requires": {
+        "editions": "1.3.4",
+        "typechecker": "4.5.0"
+      }
+    },
     "amdefine": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
       "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
       "dev": true
     },
+    "ansi-colors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
+      "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
+      "dev": true,
+      "requires": {
+        "ansi-wrap": "0.1.0"
+      }
+    },
+    "ansi-gray": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
+      "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
+      "dev": true,
+      "requires": {
+        "ansi-wrap": "0.1.0"
+      }
+    },
     "ansi-html": {
       "version": "0.0.7",
       "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
@@ -2635,6 +3775,12 @@
         "color-convert": "1.9.1"
       }
     },
+    "ansi-wrap": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
+      "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
+      "dev": true
+    },
     "anymatch": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
@@ -2645,6 +3791,21 @@
         "normalize-path": "2.1.1"
       }
     },
+    "apache-crypt": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.1.2.tgz",
+      "integrity": "sha1-ggeCozu2pf0nEggvDtOiTjybAhQ=",
+      "dev": true,
+      "requires": {
+        "unix-crypt-td-js": "1.0.0"
+      }
+    },
+    "apache-md5": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.0.6.tgz",
+      "integrity": "sha1-RwI51AxU58Mt2dbrEbw1eOzJA8I=",
+      "dev": true
+    },
     "app-root-path": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz",
@@ -2706,6 +3867,12 @@
       "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
       "dev": true
     },
+    "array-differ": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
+      "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=",
+      "dev": true
+    },
     "array-find-index": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -3097,6 +4264,15 @@
       "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
       "dev": true
     },
+    "basic-auth": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz",
+      "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "5.1.1"
+      }
+    },
     "batch": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -3542,6 +4718,54 @@
         }
       }
     },
+    "cheerio": {
+      "version": "1.0.0-rc.2",
+      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
+      "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
+      "dev": true,
+      "requires": {
+        "css-select": "1.2.0",
+        "dom-serializer": "0.1.0",
+        "entities": "1.1.1",
+        "htmlparser2": "3.9.2",
+        "lodash": "4.17.5",
+        "parse5": "3.0.3"
+      },
+      "dependencies": {
+        "domhandler": {
+          "version": "2.4.2",
+          "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+          "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+          "dev": true,
+          "requires": {
+            "domelementtype": "1.3.0"
+          }
+        },
+        "htmlparser2": {
+          "version": "3.9.2",
+          "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
+          "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
+          "dev": true,
+          "requires": {
+            "domelementtype": "1.3.0",
+            "domhandler": "2.4.2",
+            "domutils": "1.5.1",
+            "entities": "1.1.1",
+            "inherits": "2.0.3",
+            "readable-stream": "2.3.6"
+          }
+        },
+        "parse5": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
+          "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
+          "dev": true,
+          "requires": {
+            "@types/node": "8.9.5"
+          }
+        }
+      }
+    },
     "chokidar": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
@@ -3677,6 +4901,12 @@
       "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
       "dev": true
     },
+    "code-block-writer": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-7.1.0.tgz",
+      "integrity": "sha1-AMrBYl2Ek6d1JlGQkzPIHllqIUo=",
+      "dev": true
+    },
     "code-point-at": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -3722,6 +4952,12 @@
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
       "dev": true
     },
+    "color-support": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+      "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+      "dev": true
+    },
     "colors": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
@@ -3979,6 +5215,16 @@
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
       "dev": true
     },
+    "cors": {
+      "version": "2.8.4",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz",
+      "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=",
+      "dev": true,
+      "requires": {
+        "object-assign": "4.1.1",
+        "vary": "1.1.2"
+      }
+    },
     "cosmiconfig": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz",
@@ -4076,6 +5322,12 @@
         "randomfill": "1.0.4"
       }
     },
+    "csextends": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/csextends/-/csextends-1.2.0.tgz",
+      "integrity": "sha512-S/8k1bDTJIwuGgQYmsRoE+8P+ohV32WhQ0l4zqrc0XDdxOhjQQD7/wTZwCzoZX53jSX3V/qwjT+OkPTxWQcmjg==",
+      "dev": true
+    },
     "css-parse": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
@@ -4776,6 +6028,18 @@
         "domelementtype": "1.3.0"
       }
     },
+    "dot": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/dot/-/dot-1.1.2.tgz",
+      "integrity": "sha1-xzdwGfxOVQeYkosrmv62ar+h8vk=",
+      "dev": true
+    },
+    "duplexer": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+      "dev": true
+    },
     "duplexify": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
@@ -4788,6 +6052,16 @@
         "stream-shift": "1.0.0"
       }
     },
+    "eachr": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/eachr/-/eachr-3.2.0.tgz",
+      "integrity": "sha1-LDXkPqCGUW95l8+At6pk1VpKRIQ=",
+      "dev": true,
+      "requires": {
+        "editions": "1.3.4",
+        "typechecker": "4.5.0"
+      }
+    },
     "ecc-jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@@ -4798,6 +6072,12 @@
         "jsbn": "0.1.1"
       }
     },
+    "editions": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz",
+      "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==",
+      "dev": true
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -5102,6 +6382,21 @@
       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
       "dev": true
     },
+    "event-stream": {
+      "version": "3.3.4",
+      "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
+      "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
+      "dev": true,
+      "requires": {
+        "duplexer": "0.1.1",
+        "from": "0.1.7",
+        "map-stream": "0.1.0",
+        "pause-stream": "0.0.11",
+        "split": "0.3.3",
+        "stream-combiner": "0.0.4",
+        "through": "2.3.8"
+      }
+    },
     "eventemitter3": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz",
@@ -5308,6 +6603,16 @@
         }
       }
     },
+    "extendr": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/extendr/-/extendr-3.3.0.tgz",
+      "integrity": "sha512-BmBSu+KOX2XOo3XMECiekGY8VAr3O4aGYgOaHQDNg2ez5rOYW+SDfNStao4VNzr+6N27Vw3A7HJKJMrHmAAXvQ==",
+      "dev": true,
+      "requires": {
+        "editions": "1.3.4",
+        "typechecker": "4.5.0"
+      }
+    },
     "extglob": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
@@ -5317,12 +6622,34 @@
         "is-extglob": "1.0.0"
       }
     },
+    "extract-opts": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-3.3.1.tgz",
+      "integrity": "sha1-WrvtyYwNUgLjJ4cn+Rktfghsa+E=",
+      "dev": true,
+      "requires": {
+        "eachr": "3.2.0",
+        "editions": "1.3.4",
+        "typechecker": "4.5.0"
+      }
+    },
     "extsprintf": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
       "dev": true
     },
+    "fancy-log": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz",
+      "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=",
+      "dev": true,
+      "requires": {
+        "ansi-gray": "0.1.1",
+        "color-support": "1.1.3",
+        "time-stamp": "1.1.0"
+      }
+    },
     "fast-deep-equal": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
@@ -5430,6 +6757,12 @@
         "locate-path": "2.0.0"
       }
     },
+    "findit2": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz",
+      "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=",
+      "dev": true
+    },
     "flush-write-stream": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
@@ -5499,6 +6832,12 @@
       "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
       "dev": true
     },
+    "from": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
+      "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
+      "dev": true
+    },
     "from2": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
@@ -5518,6 +6857,17 @@
         "null-check": "1.0.0"
       }
     },
+    "fs-extra": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz",
+      "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "jsonfile": "4.0.0",
+        "universalify": "0.1.2"
+      }
+    },
     "fs-write-stream-atomic": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@@ -7127,6 +8477,25 @@
         }
       }
     },
+    "http-auth": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-2.4.11.tgz",
+      "integrity": "sha1-YfAkpuDnxIk0lEiVyHoTlVCcYZs=",
+      "dev": true,
+      "requires": {
+        "apache-crypt": "1.1.2",
+        "apache-md5": "1.0.6",
+        "node-uuid": "1.4.8"
+      },
+      "dependencies": {
+        "node-uuid": {
+          "version": "1.4.8",
+          "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
+          "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
+          "dev": true
+        }
+      }
+    },
     "http-deceiver": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
@@ -7515,6 +8884,22 @@
       "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==",
       "dev": true
     },
+    "ignorefs": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/ignorefs/-/ignorefs-1.2.0.tgz",
+      "integrity": "sha1-2ln7hYl25KXkNwLM0fKC/byeV1Y=",
+      "dev": true,
+      "requires": {
+        "editions": "1.3.4",
+        "ignorepatterns": "1.1.0"
+      }
+    },
+    "ignorepatterns": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz",
+      "integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4=",
+      "dev": true
+    },
     "image-size": {
       "version": "0.5.5",
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -7623,6 +9008,16 @@
       "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=",
       "dev": true
     },
+    "is-absolute": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
+      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
+      "dev": true,
+      "requires": {
+        "is-relative": "1.0.0",
+        "is-windows": "1.0.2"
+      }
+    },
     "is-accessor-descriptor": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
@@ -7781,6 +9176,12 @@
         "xtend": "4.0.1"
       }
     },
+    "is-negated-glob": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
+      "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
+      "dev": true
+    },
     "is-number": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
@@ -7875,6 +9276,15 @@
         "has": "1.0.1"
       }
     },
+    "is-relative": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
+      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
+      "dev": true,
+      "requires": {
+        "is-unc-path": "1.0.0"
+      }
+    },
     "is-stream": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
@@ -7893,6 +9303,15 @@
       "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
       "dev": true
     },
+    "is-unc-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
+      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
+      "dev": true,
+      "requires": {
+        "unc-path-regex": "0.1.2"
+      }
+    },
     "is-utf8": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@@ -8265,6 +9684,12 @@
       "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
       "dev": true
     },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
     "json-schema": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@@ -8304,6 +9729,15 @@
       "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
       "dev": true
     },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11"
+      }
+    },
     "jsonify": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
@@ -8764,6 +10198,148 @@
         "immediate": "3.0.6"
       }
     },
+    "live-server": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.1.0.tgz",
+      "integrity": "sha1-pp8ObKWB4DkapXlBlw4XwwjdSGk=",
+      "dev": true,
+      "requires": {
+        "colors": "1.3.0",
+        "connect": "3.4.1",
+        "cors": "2.8.4",
+        "event-stream": "3.3.4",
+        "faye-websocket": "0.11.1",
+        "http-auth": "2.4.11",
+        "morgan": "1.9.0",
+        "object-assign": "4.1.1",
+        "opn": "5.3.0",
+        "proxy-middleware": "0.15.0",
+        "send": "0.16.2",
+        "serve-index": "1.9.1",
+        "watchr": "2.6.0"
+      },
+      "dependencies": {
+        "colors": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz",
+          "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==",
+          "dev": true
+        },
+        "connect": {
+          "version": "3.4.1",
+          "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.1.tgz",
+          "integrity": "sha1-ohNh0/QJnvdhzabcSpc7seuwo00=",
+          "dev": true,
+          "requires": {
+            "debug": "2.2.0",
+            "finalhandler": "0.4.1",
+            "parseurl": "1.3.2",
+            "utils-merge": "1.0.0"
+          }
+        },
+        "debug": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+          "dev": true,
+          "requires": {
+            "ms": "0.7.1"
+          }
+        },
+        "faye-websocket": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
+          "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=",
+          "dev": true,
+          "requires": {
+            "websocket-driver": "0.7.0"
+          }
+        },
+        "finalhandler": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz",
+          "integrity": "sha1-haF8bFmpRxfSYtYSMNSw6+PUoU0=",
+          "dev": true,
+          "requires": {
+            "debug": "2.2.0",
+            "escape-html": "1.0.3",
+            "on-finished": "2.3.0",
+            "unpipe": "1.0.0"
+          }
+        },
+        "mime": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+          "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+          "dev": true
+        },
+        "ms": {
+          "version": "0.7.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+          "dev": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "dev": true
+        },
+        "opn": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
+          "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==",
+          "dev": true,
+          "requires": {
+            "is-wsl": "1.1.0"
+          }
+        },
+        "send": {
+          "version": "0.16.2",
+          "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+          "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+          "dev": true,
+          "requires": {
+            "debug": "2.6.9",
+            "depd": "1.1.2",
+            "destroy": "1.0.4",
+            "encodeurl": "1.0.2",
+            "escape-html": "1.0.3",
+            "etag": "1.8.1",
+            "fresh": "0.5.2",
+            "http-errors": "1.6.3",
+            "mime": "1.4.1",
+            "ms": "2.0.0",
+            "on-finished": "2.3.0",
+            "range-parser": "1.2.0",
+            "statuses": "1.4.0"
+          },
+          "dependencies": {
+            "debug": {
+              "version": "2.6.9",
+              "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+              "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+              "dev": true,
+              "requires": {
+                "ms": "2.0.0"
+              }
+            },
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+              "dev": true
+            }
+          }
+        },
+        "utils-merge": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
+          "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=",
+          "dev": true
+        }
+      }
+    },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -8830,6 +10406,12 @@
       "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
       "dev": true
     },
+    "lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+      "dev": true
+    },
     "lodash.mergewith": {
       "version": "4.6.1",
       "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
@@ -8950,6 +10532,18 @@
         "yallist": "2.1.2"
       }
     },
+    "lunr": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.2.0.tgz",
+      "integrity": "sha512-HNuCi0meEZZpfORgJuxmXu5yHkdWrWew9dphUj+ouCbNNT38ud5YXWKVcLo1gSwQcDdKMXn+lRuDiXLfllnnjA==",
+      "dev": true
+    },
+    "macos-release": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-1.1.0.tgz",
+      "integrity": "sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA==",
+      "dev": true
+    },
     "make-dir": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz",
@@ -8977,6 +10571,12 @@
       "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
       "dev": true
     },
+    "map-stream": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
+      "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
+      "dev": true
+    },
     "map-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@@ -8986,6 +10586,12 @@
         "object-visit": "1.0.1"
       }
     },
+    "marked": {
+      "version": "0.3.19",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
+      "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
+      "dev": true
+    },
     "math-random": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
@@ -9238,6 +10844,19 @@
         "minimist": "0.0.8"
       }
     },
+    "morgan": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz",
+      "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=",
+      "dev": true,
+      "requires": {
+        "basic-auth": "2.0.0",
+        "debug": "2.6.9",
+        "depd": "1.1.2",
+        "on-finished": "2.3.0",
+        "on-headers": "1.0.1"
+      }
+    },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -9274,6 +10893,18 @@
       "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
       "dev": true
     },
+    "multimatch": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
+      "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=",
+      "dev": true,
+      "requires": {
+        "array-differ": "1.0.0",
+        "array-union": "1.0.2",
+        "arrify": "1.0.1",
+        "minimatch": "3.0.4"
+      }
+    },
     "nan": {
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
@@ -9880,6 +11511,16 @@
         "lcid": "1.0.0"
       }
     },
+    "os-name": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/os-name/-/os-name-2.0.1.tgz",
+      "integrity": "sha1-uaOGNhwXrjohc27wWZQFyajF3F4=",
+      "dev": true,
+      "requires": {
+        "macos-release": "1.1.0",
+        "win-release": "1.1.1"
+      }
+    },
     "os-tmpdir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -10094,6 +11735,15 @@
         "pify": "3.0.0"
       }
     },
+    "pause-stream": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
+      "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
+      "dev": true,
+      "requires": {
+        "through": "2.3.8"
+      }
+    },
     "pbkdf2": {
       "version": "3.0.14",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
@@ -10464,6 +12114,12 @@
         "ipaddr.js": "1.6.0"
       }
     },
+    "proxy-middleware": {
+      "version": "0.15.0",
+      "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz",
+      "integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=",
+      "dev": true
+    },
     "prr": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -11040,6 +12696,16 @@
         "ret": "0.1.15"
       }
     },
+    "safefs": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/safefs/-/safefs-4.1.0.tgz",
+      "integrity": "sha1-+CrrS9165R9lPrIPZyizBYyNZEU=",
+      "dev": true,
+      "requires": {
+        "editions": "1.3.4",
+        "graceful-fs": "4.1.11"
+      }
+    },
     "sass-graph": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
@@ -11080,6 +12746,38 @@
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
       "dev": true
     },
+    "scandirectory": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/scandirectory/-/scandirectory-2.5.0.tgz",
+      "integrity": "sha1-bOA/VKCQtmjjy+2/IO354xBZPnI=",
+      "dev": true,
+      "requires": {
+        "ignorefs": "1.2.0",
+        "safefs": "3.2.2",
+        "taskgroup": "4.3.1"
+      },
+      "dependencies": {
+        "safefs": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/safefs/-/safefs-3.2.2.tgz",
+          "integrity": "sha1-gXDBRE1wOOCMrqBaN0+uL6NJ4Vw=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11"
+          }
+        },
+        "taskgroup": {
+          "version": "4.3.1",
+          "resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-4.3.1.tgz",
+          "integrity": "sha1-feGT/r12gnPEV3MElwJNUSwnkVo=",
+          "dev": true,
+          "requires": {
+            "ambi": "2.5.0",
+            "csextends": "1.2.0"
+          }
+        }
+      }
+    },
     "schema-utils": {
       "version": "0.4.5",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz",
@@ -11757,6 +13455,15 @@
         "wbuf": "1.7.3"
       }
     },
+    "split": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
+      "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
+      "dev": true,
+      "requires": {
+        "through": "2.3.8"
+      }
+    },
     "split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -11860,6 +13567,15 @@
         "readable-stream": "2.3.6"
       }
     },
+    "stream-combiner": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
+      "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
+      "dev": true,
+      "requires": {
+        "duplexer": "0.1.1"
+      }
+    },
     "stream-each": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz",
@@ -12040,6 +13756,18 @@
         "inherits": "2.0.3"
       }
     },
+    "taskgroup": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-5.0.1.tgz",
+      "integrity": "sha1-CHNsmyRoOxQ0d0Ix60tzqnw/ebU=",
+      "dev": true,
+      "requires": {
+        "ambi": "2.5.0",
+        "eachr": "3.2.0",
+        "editions": "1.3.4",
+        "extendr": "3.3.0"
+      }
+    },
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -12062,6 +13790,12 @@
       "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=",
       "dev": true
     },
+    "time-stamp": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
+      "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
+      "dev": true
+    },
     "timers-browserify": {
       "version": "2.0.10",
       "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
@@ -12163,6 +13897,12 @@
         }
       }
     },
+    "traverse": {
+      "version": "0.6.6",
+      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
+      "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=",
+      "dev": true
+    },
     "tree-kill": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz",
@@ -12240,6 +13980,88 @@
         }
       }
     },
+    "ts-simple-ast": {
+      "version": "11.2.0",
+      "resolved": "https://registry.npmjs.org/ts-simple-ast/-/ts-simple-ast-11.2.0.tgz",
+      "integrity": "sha1-WKRGneU5HF4fnV9etX1nXB4xVjY=",
+      "dev": true,
+      "requires": {
+        "@dsherret/to-absolute-glob": "2.0.2",
+        "@types/fs-extra": "5.0.3",
+        "code-block-writer": "7.1.0",
+        "fs-extra": "5.0.0",
+        "glob-parent": "3.1.0",
+        "globby": "6.1.0",
+        "is-negated-glob": "1.0.0",
+        "multimatch": "2.1.0",
+        "object-assign": "4.1.1",
+        "tslib": "1.9.0",
+        "typescript": "2.8.3"
+      },
+      "dependencies": {
+        "fs-extra": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
+          "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "jsonfile": "4.0.0",
+            "universalify": "0.1.2"
+          }
+        },
+        "glob-parent": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+          "dev": true,
+          "requires": {
+            "is-glob": "3.1.0",
+            "path-dirname": "1.0.2"
+          }
+        },
+        "globby": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+          "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+          "dev": true,
+          "requires": {
+            "array-union": "1.0.2",
+            "glob": "7.1.2",
+            "object-assign": "4.1.1",
+            "pify": "2.3.0",
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "is-extglob": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+          "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+          "dev": true
+        },
+        "is-glob": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+          "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+          "dev": true,
+          "requires": {
+            "is-extglob": "2.1.1"
+          }
+        },
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        },
+        "typescript": {
+          "version": "2.8.3",
+          "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz",
+          "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==",
+          "dev": true
+        }
+      }
+    },
     "tsickle": {
       "version": "0.27.5",
       "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.27.5.tgz",
@@ -12369,6 +14191,15 @@
         "mime-types": "2.1.18"
       }
     },
+    "typechecker": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-4.5.0.tgz",
+      "integrity": "sha512-bqPE/ck3bVIaXP7gMKTKSHrypT32lpYTpiqzPYeYzdSQnmaGvaGhy7TnN/M/+5R+2rs/kKcp9ZLPRp/Q9Yj+4w==",
+      "dev": true,
+      "requires": {
+        "editions": "1.3.4"
+      }
+    },
     "typedarray": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -12452,6 +14283,12 @@
       "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=",
       "dev": true
     },
+    "unc-path-regex": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
+      "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
+      "dev": true
+    },
     "union-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
@@ -12505,6 +14342,18 @@
         "imurmurhash": "0.1.4"
       }
     },
+    "universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+      "dev": true
+    },
+    "unix-crypt-td-js": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.0.0.tgz",
+      "integrity": "sha1-HAgkFQSBvHoB1J6Y8exmjYJBLzs=",
+      "dev": true
+    },
     "unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -12767,6 +14616,12 @@
         }
       }
     },
+    "viz.js": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-1.8.2.tgz",
+      "integrity": "sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==",
+      "dev": true
+    },
     "vm-browserify": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
@@ -13135,6 +14990,22 @@
         }
       }
     },
+    "watchr": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/watchr/-/watchr-2.6.0.tgz",
+      "integrity": "sha1-51xCOxC+eSZ6DD73bi6hBP4CZ6U=",
+      "dev": true,
+      "requires": {
+        "eachr": "3.2.0",
+        "extendr": "3.3.0",
+        "extract-opts": "3.3.1",
+        "ignorefs": "1.2.0",
+        "safefs": "4.1.0",
+        "scandirectory": "2.5.0",
+        "taskgroup": "5.0.1",
+        "typechecker": "4.5.0"
+      }
+    },
     "wbuf": {
       "version": "1.7.3",
       "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
@@ -14141,6 +16012,15 @@
         "string-width": "1.0.2"
       }
     },
+    "win-release": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz",
+      "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=",
+      "dev": true,
+      "requires": {
+        "semver": "5.5.0"
+      }
+    },
     "window-size": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
diff --git a/web/gui2/package.json b/web/gui2/package.json
index 0770d71..f122bb7 100644
--- a/web/gui2/package.json
+++ b/web/gui2/package.json
@@ -9,7 +9,7 @@
     "test": "ng test",
     "lint": "ng lint",
     "e2e": "ng e2e",
-    "compodoc": "./node_modules/.bin/compodoc -p src/main/webapp/tsconfig.app.json"
+    "compodoc": "./node_modules/.bin/compodoc -p src/main/webapp/tsconfig.app.json -n 'ONOS GUI2 Documentation' --includes ."
   },
   "private": true,
   "dependencies": {
@@ -32,6 +32,7 @@
     "@angular/cli": "~6.0.0",
     "@angular/compiler-cli": "^6.0.0",
     "@angular/language-service": "^6.0.0",
+    "@compodoc/compodoc": "^1.1.3",
     "@types/jasmine": "~2.8.6",
     "@types/jasminewd2": "~2.0.3",
     "@types/node": "~8.9.4",
diff --git a/web/gui2/src/main/webapp/app/fw/layer/detailspanel.service.ts b/web/gui2/src/main/webapp/app/fw/layer/detailspanel.service.ts
deleted file mode 100644
index 5699262..0000000
--- a/web/gui2/src/main/webapp/app/fw/layer/detailspanel.service.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017-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 { EditableTextService } from '../layer/editabletext.service';
-import { FnService } from '../util/fn.service';
-import { IconService } from '../svg/icon.service';
-import { LogService } from '../../log.service';
-import { MastService } from '../mast/mast.service';
-import { PanelService } from './panel.service';
-import { WebSocketService } from '../remote/websocket.service';
-
-/**
- * ONOS GUI -- Layer -- Details Panel Service
- */
-@Injectable()
-export class DetailsPanelService {
-
-  constructor(
-      private ets: EditableTextService,
-      private fs: FnService,
-      private is: IconService,
-      private log: LogService,
-      private mast: MastService,
-      private panel: PanelService,
-      private wss: WebSocketService
-  ) {
-      this.log.debug('DetailsPanelService constructed');
-  }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts b/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts
index d6c37bb..378cd9b 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts
@@ -19,21 +19,21 @@
 import { FnService } from '../util/fn.service';
 import { KeyService } from '../util/key.service';
 import { LogService } from '../../log.service';
-import { PanelService } from './panel.service';
 
 /**
  * ONOS GUI -- Layer -- Dialog Service
  *
  * Builds on the panel service to provide dialog functionality.
  */
-@Injectable()
+@Injectable({
+  providedIn: 'root',
+})
 export class DialogService {
 
   constructor(
     private fs: FnService,
     private ks: KeyService,
     private log: LogService,
-    private ps: PanelService,
   ) {
     this.log.debug('DialogService constructed');
   }
diff --git a/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts b/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
index b4e39b2..4e8abe7 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/layer.module.ts
@@ -17,11 +17,9 @@
 import { CommonModule } from '@angular/common';
 
 import { FlashComponent } from './flash/flash.component';
-import { DetailsPanelService } from './detailspanel.service';
 import { DialogService } from './dialog.service';
 import { EditableTextService } from './editabletext.service';
 import { LoadingService } from './loading.service';
-import { PanelService } from './panel.service';
 import { QuickHelpService } from './quickhelp.service';
 import { VeilComponent } from './veil/veil.component';
 
@@ -41,12 +39,6 @@
     VeilComponent
   ],
   providers: [
-    DetailsPanelService,
-    DialogService,
-    EditableTextService,
-    LoadingService,
-    PanelService,
-    QuickHelpService
   ]
 })
 export class LayerModule { }
diff --git a/web/gui2/src/main/webapp/app/fw/layer/panel.service.ts b/web/gui2/src/main/webapp/app/fw/layer/panel.service.ts
deleted file mode 100644
index 7ccb7d9..0000000
--- a/web/gui2/src/main/webapp/app/fw/layer/panel.service.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-import { Injectable } from '@angular/core';
-import { FnService } from '../util/fn.service';
-import { LogService } from '../../log.service';
-
-/**
- * ONOS GUI -- Layer -- Panel Service
- */
-@Injectable()
-export class PanelService {
-
-  constructor(
-    private fs: FnService,
-    private log: LogService
-  ) {
-    this.log.debug('PanelService constructed');
-  }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/layer/quickhelp.service.ts b/web/gui2/src/main/webapp/app/fw/layer/quickhelp.service.ts
index f433a2b..b515180 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/quickhelp.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/quickhelp.service.ts
@@ -24,7 +24,9 @@
  *
  * Provides a mechanism to display key bindings and mouse gesture notes.
  */
-@Injectable()
+@Injectable({
+  providedIn: 'root',
+})
 export class QuickHelpService {
 
   constructor(
diff --git a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts
index 0ed493f..caa52ec 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/veil/veil.component.spec.ts
@@ -18,32 +18,55 @@
  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 '../../../fw/util/fn.service';
 import { LogService } from '../../../log.service';
 import { KeyService } from '../../util/key.service';
 import { GlyphService } from '../../svg/glyph.service';
+import { of } from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
 
 class MockKeyService {}
 
 class MockGlyphService {}
 
 describe('VeilComponent', () => {
-    let log: LogService;
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
 
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
+    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: LogService, useValue: log },
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
                 { provide: KeyService, useClass: MockKeyService },
                 { provide: GlyphService, useClass: MockGlyphService },
+                { provide: 'Window', useValue: windowMock },
             ]
         });
-    });
+        logServiceSpy = TestBed.get(LogService);
+    }));
 
     it('should create', () => {
         const fixture = TestBed.createComponent(VeilComponent);
diff --git a/web/gui2/src/main/webapp/app/fw/mast/mast.service.ts b/web/gui2/src/main/webapp/app/fw/mast/mast.service.ts
index d357458..34a3d8b 100644
--- a/web/gui2/src/main/webapp/app/fw/mast/mast.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/mast/mast.service.ts
@@ -17,7 +17,7 @@
 import { FnService } from '../util/fn.service';
 import { LogService } from '../../log.service';
 
-const padMobile = 16;
+const PADMOBILE = 16;
 
 /**
  * ONOS GUI -- Masthead Service
@@ -32,7 +32,7 @@
     private log: LogService
   ) {
     if (this.fs.isMobile()) {
-        this.mastHeight += padMobile;
+        this.mastHeight += PADMOBILE;
     }
 
     this.log.debug('MastService constructed');
diff --git a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.css b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.css
index b5f1e5f..5b2d464 100644
--- a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.css
+++ b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.css
@@ -17,10 +17,20 @@
 /*
  ONOS GUI -- Masthead (layout) -- CSS file
  */
+#mast-top-block {
+    display: block;
+    z-index: -1;
+    height: 48px;
+    width: 100%;
+}
 
 #mast {
+    position: absolute;
+    width: 100%;
+    top: 0px;
     height: 48px;
     padding: 0;
+    z-index: 10000;
 }
 
 #mast a:hover {
diff --git a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.html b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.html
index e7d0995..72242ba 100644
--- a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.html
+++ b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.html
@@ -13,6 +13,12 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
+<div id="mast-top-block"></div>
+<!-- The mast-top-block is an inline display element that pushes any
+  subsequent elements down the page. It has a height of 48px
+     The mast block overlays the mast-top-block. It is is positioned
+     absolutely so that the nav component can slide in behind it when
+     not shown -->
 <div id="mast" align="left">
     <span class="nav-menu-button clickable" (click)="ns.toggleNav()">
         <img src="data/img/nav-menu-mojo.png"/>
@@ -21,17 +27,14 @@
     <div id="mast-right">
         <nav>
             <div class="dropdown-parent">
-                <a class="clickable user-menu__name">{{username}} <i class="dropdown-icon"></i></a>
+                <a class="clickable user-menu__name">{{ username }} <i class="dropdown-icon"></i></a>
                 <div class="dropdown">
-                    <!--<a href="rs/logout"> {{getLion('logout')}} </a> !!FIXME-->
-                    <a href="rs/logout">logout</a>
+                    <a href="rs/logout"> {{ lionFn('logout') }} </a>
                 </div>
             </div>
             <div class="ctrl-btns">
-                <div class="active clickable icon"
-                     tooltip tt-msg="helpTip"
-                     ng-click="directTo()">
-                    <onos-icon iconId="query" iconSize="32"></onos-icon>
+                <div class="active clickable icon" (click)="directTo()">
+                    <onos-icon iconId="query" iconSize="32" toolTip="{{ lionFn('tt_help') }}"></onos-icon>
                 </div>
             </div>
         </nav>
diff --git a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.spec.ts b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.spec.ts
new file mode 100644
index 0000000..3d061a5
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.spec.ts
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+import { LogService } from '../../../log.service';
+import { ConsoleLoggerService } from '../../../consolelogger.service';
+import { MastComponent } from './mast.component';
+import { IconComponent } from '../../svg/icon/icon.component';
+import { LionService } from '../../util/lion.service';
+import { IconService } from '../../svg/icon.service';
+import { NavService } from '../../nav/nav.service';
+import { WebSocketService } from '../../remote/websocket.service';
+
+class MockNavService {}
+
+class MockIconService {
+    loadIconDef() {}
+}
+
+class MockWebSocketService {
+    createWebSocket() {}
+    isConnected() { return false; }
+    unbindHandlers() {}
+    bindHandlers() {}
+}
+
+/**
+ * ONOS GUI -- Masthead Controller - Unit Tests
+ */
+describe('MastComponent', () => {
+    let log: LogService;
+    let windowMock: Window;
+    let component: MastComponent;
+    let fixture: ComponentFixture<MastComponent>;
+    const bundleObj = {
+        'core.view.App': {
+            test: 'test1',
+            tt_help: 'Help!'
+        }
+    };
+    const mockLion = (key) =>  {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        windowMock = <any>{
+            location: <any> {
+                hostname: 'foo',
+                pathname: 'apps',
+                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'
+            }
+        };
+
+        TestBed.configureTestingModule({
+            declarations: [ MastComponent, IconComponent ],
+            providers: [
+                { provide: LogService, useValue: log },
+                { provide: NavService, useClass: MockNavService },
+                { provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: IconService, useClass: MockIconService },
+                { provide: WebSocketService, useClass: MockWebSocketService },
+                { provide: 'Window', useValue: windowMock }
+            ]
+        })
+        .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(MastComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should have a div#mast-top-block', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#mast-top-block'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a span.nav-menu-button inside a div#mast', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#mast span.nav-menu-button'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a div.dropdown-parent inside a div#mast-right inside a div#mast', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#mast div#mast-right div.dropdown-parent'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual('  %logout% ');
+    });
+
+    it('should have an onos-icon inside a div#mast-right inside a div#mast', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#mast div#mast-right div.ctrl-btns div.active onos-icon'));
+        expect(divDe).toBeTruthy();
+    });
+
+});
diff --git a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.ts b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.ts
index a8b85a4..2cb110f 100644
--- a/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.ts
+++ b/web/gui2/src/main/webapp/app/fw/mast/mast/mast.component.ts
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
-import { DialogService } from '../../layer/dialog.service';
+import { Component, Input, OnInit, OnDestroy, Inject } from '@angular/core';
 import { LionService } from '../../util/lion.service';
 import { LogService } from '../../../log.service';
 import { NavService } from '../../nav/nav.service';
-import { WebSocketService } from '../../remote/websocket.service';
 
 /**
  * ONOS GUI -- Masthead Component
@@ -28,39 +26,67 @@
   templateUrl: './mast.component.html',
   styleUrls: ['./mast.component.css', './mast.theme.css']
 })
-export class MastComponent implements OnInit {
-    public username;
+export class MastComponent implements OnInit, OnDestroy {
+    @Input() username: string;
+
+    lionFn; // Function
+    viewMap = new Map<string, string>([]); // A map of app names
 
     constructor(
-        private ds: DialogService,
-        private ls: LionService,
+        private lion: LionService,
         private log: LogService,
         public ns: NavService,
-        private wss: WebSocketService
+        @Inject('Window') private window: Window,
     ) {
-        this.log.debug('MastComponent constructed');
-
+        this.viewMap.set('apps', 'https://wiki.onosproject.org/display/ONOS/GUI+Application+View');
+        this.viewMap.set('device', 'https://wiki.onosproject.org/display/ONOS/GUI+Device+View');
+        this.viewMap.set('', 'https://wiki.onosproject.org/display/ONOS/The+ONOS+Web+GUI');
     }
 
     ngOnInit() {
-        // onosUser is a global set via the index.html generated source
-        // TODO: Fix onosuser below to get it from index.html like before
-        // TODO: Fix the lionService
-        this.username = 'onosUser'; // || this.getLion('unknown_user');
-
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('mast', () => this.doLion());
+            this.log.debug('LION not available when MastComponent initialized');
+        } else {
+            this.doLion();
+        }
         this.log.debug('MastComponent initialized');
     }
 
-
-
-    /* In the case of Masthead, we cannot cache the lion bundle, because we
-     * call too early (before the lion data is uploaded from the server).
-     * So we'll dig into the lion service for each request...
+    /**
+     * Nav component should never be closed, but in case it does, it's
+     * safer to tidy up after itself
      */
-    getLion(x: string): string {
-      // lion is a function that takes a string and returns a string
-      const lion: (string) => string  = this.ls.bundle('core.fw.Mast');
-      return lion(x);
+    ngOnDestroy() {
+        this.lion.loadCbs.delete('mast');
     }
 
+    /**
+    * Read the LION bundle for App and set up the lionFn
+    */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.fw.Mast');
+        if (this.username === undefined) {
+            this.username = this.lionFn('unknown_user');
+        }
+    }
+
+    /**
+    * 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 + '%';
+    }
+
+    directTo() {
+        const curId = this.window.location.pathname.replace('/', '');
+        let helpUrl: string = this.viewMap.get(curId);
+        if (helpUrl === undefined) {
+            helpUrl = this.viewMap.get('');
+            this.log.warn('No help file linked for view:', curId);
+        }
+        this.window.open(helpUrl);
+    }
 }
diff --git a/web/gui2/src/main/webapp/app/fw/nav/nav.module.ts b/web/gui2/src/main/webapp/app/fw/nav/nav.module.ts
index d25237f..65f222d 100644
--- a/web/gui2/src/main/webapp/app/fw/nav/nav.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/nav/nav.module.ts
@@ -22,9 +22,6 @@
 import { NavComponent } from './nav/nav.component';
 import { NavService } from './nav.service';
 
-import { AppsComponent } from '../../view/apps/apps.component';
-import { DeviceComponent } from '../../view/device/device.component';
-
 /**
  * ONOS GUI -- Navigation Module
  */
diff --git a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.css b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.css
index 3d2f646..5030be8 100644
--- a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.css
+++ b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.css
@@ -24,7 +24,6 @@
     left: 0;
     padding: 0;
     z-index: 3000;
-    visibility: hidden;
 }
 
 html[data-platform='iPad'] #nav {
diff --git a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.html b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.html
index f61a9ab..cb48492 100644
--- a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.html
+++ b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.html
@@ -13,13 +13,16 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<nav id="nav" [@navState]="ns.showNav?'active':'inactive'">
-    <div class="nav-hdr">Platform</div>
+<nav id="nav" [@navState]="ns.showNav">
+    <div class="nav-hdr">{{ lionFn('cat_platform') }}</div>
+
     <a (click)="ns.hideNav()" routerLink="/apps" routerLinkActive="active">
-        <!--<div onosIcon iconId="nav_apps"></div>
-         Using the new IconComponent rather than the directive -->
         <onos-icon iconId="nav_apps"></onos-icon>Apps</a>
-    <div class="nav-hdr">Network</div>
+
+    <div class="nav-hdr">{{ lionFn('cat_network') }}</div>
+
     <a (click)="ns.hideNav()" routerLink="/devices" routerLinkActive="active">
         <onos-icon iconId="nav_devs"></onos-icon>Devices</a>
+
+    <div class="nav-hdr">{{ lionFn('cat_other') }}</div>
 </nav>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.spec.ts b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.spec.ts
new file mode 100644
index 0000000..e2bd999
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.spec.ts
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Params } from '@angular/router';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { of } from 'rxjs';
+
+import { ConsoleLoggerService } from '../../../consolelogger.service';
+import { FnService } from '../../../fw/util/fn.service';
+import { IconComponent } from '../../svg/icon/icon.component';
+import { IconService } from '../../svg/icon.service';
+import { LionService } from '../../util/lion.service';
+import { LogService } from '../../../log.service';
+import { NavComponent } from './nav.component';
+import { NavService } from '../nav.service';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+class MockNavService {}
+
+class MockIconService {
+    loadIconDef() {}
+}
+
+/**
+ * ONOS GUI -- Util -- Navigation Component - Unit Tests
+ */
+describe('NavComponent', () => {
+    let log: LogService;
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let component: NavComponent;
+    let fixture: ComponentFixture<NavComponent>;
+    const bundleObj = {
+        'core.view.App': {
+            test: 'test1'
+        }
+    };
+    const mockLion = (key) =>  {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        log = new ConsoleLoggerService();
+        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, log, windowMock);
+
+        TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
+            declarations: [ NavComponent, IconComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: IconService, useClass: MockIconService },
+                { provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: LogService, useValue: log },
+                { provide: NavService, useClass: MockNavService },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(NavComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should have a nav#nav', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('nav#nav'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a platform div.nav-hdr inside a nav#nav', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('nav#nav div.nav-hdr'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual('%cat_platform%');
+    });
+
+    it('should have an apps link inside a nav#nav', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('nav#nav a'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual('Apps');
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.ts b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.ts
index 27ae8de..7614e67 100644
--- a/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.ts
+++ b/web/gui2/src/main/webapp/app/fw/nav/nav/nav.component.ts
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+
+import { LionService } from '../../util/lion.service';
 import { LogService } from '../../../log.service';
 import { NavService } from '../nav.service';
-import { trigger, state, style, animate, transition } from '@angular/animations';
 
 /**
  * ONOS GUI -- Navigation Module
@@ -27,30 +29,64 @@
   styleUrls: ['./nav.theme.css', './nav.component.css'],
   animations: [
     trigger('navState', [
-      state('inactive', style({
-        visibility: 'hidden',
-        transform: 'translateX(-100%)'
+      state('false', style({
+        transform: 'translateY(-100%)'
       })),
-      state('active', style({
-        visibility: 'visible',
-        transform: 'translateX(0%)'
+      state('true', style({
+        transform: 'translateY(0%)'
       })),
-      transition('inactive => active', animate('100ms ease-in')),
-      transition('active => inactive', animate('100ms ease-out'))
+      transition('0 => 1', animate('100ms ease-in')),
+      transition('1 => 0', animate('100ms ease-out'))
     ])
   ]
 })
-export class NavComponent implements OnInit {
+export class NavComponent implements OnInit, OnDestroy {
+    lionFn; // Function
 
-  constructor(
-    private log: LogService,
-    public ns: NavService
-  ) {
-    this.log.debug('NavComponent constructed');
-  }
+    constructor(
+        private log: LogService,
+        private lion: LionService,
+        public ns: NavService
+    ) {
+        this.log.debug('NavComponent constructed');
+    }
 
-  ngOnInit() {
-    this.log.debug('NavComponent initialized');
-  }
+    /**
+     * If LION is not ready we make do with a dummy function
+     * As soon a lion gets loaded this function will be replaced with
+     * the real thing
+     */
+    ngOnInit() {
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('nav', () => this.doLion());
+            this.log.debug('LION not available when NavComponent initialized');
+        } else {
+            this.doLion();
+        }
+    }
+
+    /**
+     * Nav component should never be closed, but in case it does, it's
+     * safer to tidy up after itself
+     */
+    ngOnDestroy() {
+        this.lion.loadCbs.delete('nav');
+    }
+
+    /**
+    * Read the LION bundle for App and set up the lionFn
+    */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.fw.Nav');
+    }
+
+    /**
+    * 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/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts b/web/gui2/src/main/webapp/app/fw/remote/urlfn.service.spec.ts
similarity index 93%
rename from web/gui2/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts
rename to web/gui2/src/main/webapp/app/fw/remote/urlfn.service.spec.ts
index 0aa14ea..2b07f1c 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/remote/urlfn.service.spec.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/urlfn.service.spec.ts
@@ -15,10 +15,10 @@
  */
 import { TestBed, inject } from '@angular/core/testing';
 
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { UrlFnService } from '../../../../app/fw/remote/urlfn.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
+import { LogService } from '../../log.service';
+import { ConsoleLoggerService } from '../../consolelogger.service';
+import { UrlFnService } from './urlfn.service';
+import { FnService } from '../util/fn.service';
 import { ActivatedRoute, Params } from '@angular/router';
 import { of } from 'rxjs';
 
diff --git a/web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.spec.ts
similarity index 95%
rename from web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts
rename to web/gui2/src/main/webapp/app/fw/remote/websocket.service.spec.ts
index e0c7675..57d8d34 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/remote/websocket.service.spec.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.spec.ts
@@ -15,13 +15,13 @@
  */
 import { TestBed, inject } from '@angular/core/testing';
 
-import { LogService } from '../../../../app/log.service';
-import { WebSocketService, WsOptions, Callback, EventType } from '../../../../app/fw/remote/websocket.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-import { GlyphService } from '../../../../app/fw/svg/glyph.service';
+import { LogService } from '../../log.service';
+import { WebSocketService, WsOptions, Callback, EventType } from './websocket.service';
+import { FnService } from '../util/fn.service';
+import { GlyphService } from '../svg/glyph.service';
 import { ActivatedRoute, Params } from '@angular/router';
-import { UrlFnService } from '../../../../app/fw/remote/urlfn.service';
-import { WSock } from '../../../../app/fw/remote/wsock.service';
+import { UrlFnService } from './urlfn.service';
+import { WSock } from './wsock.service';
 import { of } from 'rxjs';
 
 class MockActivatedRoute extends ActivatedRoute {
@@ -101,7 +101,8 @@
             'noHandlersWarn', 'resetState',
             'createWebSocket', 'bindHandlers', 'unbindHandlers',
             'addOpenListener', 'removeOpenListener', 'sendEvent',
-            'setVeilDelegate', 'setLoadingDelegate', 'isConnected', 'closeWebSocket'
+            'setVeilDelegate', 'setLoadingDelegate', 'isConnected',
+             'closeWebSocket', 'isHandling'
         ])).toBeTruthy();
     });
 
diff --git a/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
index 18dd734..b5eeaa6 100644
--- a/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
@@ -385,6 +385,10 @@
         }
     }
 
+    isHandling(handlerId: string): boolean {
+        return this.handlers.get(handlerId) !== undefined;
+    }
+
     /**
      * Add a listener function for listening for WebSocket opening.
      * The function must give a host and url and return void
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts b/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts
index c6a81e9..813589f 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon.service.ts
@@ -191,7 +191,7 @@
      */
     loadIconDef(iconCls: string): void {
         this.gs.loadDefs(this.ensureIconLibDefs(), [glyphMapping.get(iconCls)], true);
-        this.log.debug('icon defintion', iconCls, 'added to defs');
+        this.log.debug('icon definition', iconCls, 'added to defs');
     }
 
 
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts
index c851f47..eabf0fd 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit, Input } from '@angular/core';
+import { Component, OnInit, OnChanges, Input } from '@angular/core';
 import { IconService, glyphMapping } from '../icon.service';
 import { LogService } from '../../../log.service';
 
@@ -36,7 +36,7 @@
     './tooltip.css', './tooltip-theme.css'
     ]
 })
-export class IconComponent implements OnInit {
+export class IconComponent implements OnInit, OnChanges {
     @Input() iconId: string;
     @Input() iconSize: number = 20;
     @Input() toolTip: string = undefined;
@@ -53,11 +53,23 @@
         this.log.debug('IconComponent constructed');
     }
 
+    /**
+     * Icons are loaded in to the DOM under iconDefs
+     * TODO: Change this to use more standard Angular 6 mechanism
+     */
     ngOnInit() {
         this.is.loadIconDef(this.iconId);
     }
 
     /**
+     * This is needed in case the iconId changes while icon component
+     * is displayed on screen.
+     */
+    ngOnChanges() {
+        this.is.loadIconDef(this.iconId);
+    }
+
+    /**
      * Get the corresponding iconTag from the glyphMapping in the iconService
      * @returns The iconTag corresponding to the iconId of this instance
      */
diff --git a/web/gui2/src/main/webapp/app/fw/util/lion.service.ts b/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
index 248fe51..2416e7d 100644
--- a/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
@@ -35,7 +35,7 @@
 export class LionService {
 
     ubercache: any[] = [];
-    loadCb; // Function
+    loadCbs = new Map<string, () => void>([]); // A map of functions
 
     /**
      * Handler for uberlion event from WSS
@@ -51,9 +51,11 @@
                 this.log.info('            :=> ', p);
             }
         }
-        if (this.loadCb) {
-            this.log.debug('Calling the load callback');
-            this.loadCb();
+        // If any component had registered a callback, call it now
+        // that LION is loaded
+        for (const cbname of this.loadCbs.keys()) {
+            this.log.debug('Updating', cbname, 'with LION');
+            this.loadCbs.get(cbname)();
         }
 
         this.log.debug('LION service: uber-lion bundle received:', data);
diff --git a/web/gui2/src/main/webapp/app/fw/widget/detailspanel.base.ts b/web/gui2/src/main/webapp/app/fw/widget/detailspanel.base.ts
new file mode 100644
index 0000000..d7cf7b8
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/detailspanel.base.ts
@@ -0,0 +1,113 @@
+/*
+ * 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 } from '../util/fn.service';
+import { LoadingService } from '../layer/loading.service';
+import { LogService } from '../../log.service';
+import { WebSocketService } from '../remote/websocket.service';
+
+import { PanelBaseImpl } from './panel.base';
+
+/**
+ * A generic model of the data returned from the *DetailsResponse
+ */
+interface DetailsResponse {
+    details: any;
+}
+
+/**
+ * Extends the PanelBase abstract class specifically for showing details
+ *
+ * This makes another call through WSS to the server for specific
+ * details to fill the panel with
+ *
+ * This replaces the detailspanel service in the old gui
+ */
+export abstract class DetailsPanelBaseImpl extends PanelBaseImpl {
+
+    private root: string;
+    private req: string;
+    private resp: string;
+    private handlers: string[] = [];
+    public detailsData: any = {};
+    public closed: boolean = false;
+
+    constructor(
+        protected fs: FnService,
+        protected ls: LoadingService,
+        protected log: LogService,
+        protected wss: WebSocketService,
+        protected tag: string,
+    ) {
+        super(fs, ls, log, wss, {});
+        this.root = tag + 's';
+        this.req = tag + 'DetailsRequest';
+        this.resp = tag + 'DetailsResponse';
+    }
+
+    /**
+     * When the details panel is created set up a listener on
+     * Web Socket for details responses
+     */
+    init() {
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            [this.resp, (data) => this.detailsPanelResponseCb(data)]
+        ]));
+        this.handlers.push(this.resp);
+    }
+
+    /**
+     * When the details panel is destroyed this should be called to
+     * de-register from the WebSocket
+     */
+    destroy() {
+        this.wss.unbindHandlers(this.handlers);
+    }
+
+    /**
+     * A callback that executes when the details data that was requested
+     * on WebSocketService arrives.
+     */
+    detailsPanelResponseCb(data: DetailsResponse) {
+        this.detailsData = data['details'];
+    }
+
+    /**
+     * Details Panel Data Request - should be called whenever id changes
+     * If id is empty, no request is made
+     */
+    requestDetailsPanelData(id: string) {
+        if (id === '') {
+            return;
+        }
+        this.closed = false;
+        const query = {'id': id};
+
+        // Do not send if the Web Socket hasn't opened
+        if (this.wss.isConnected()) {
+            if (this.fs.debugOn('panel')) {
+                this.log.debug('Details panel data REQUEST:', this.req, query);
+            }
+            this.wss.sendEvent(this.req, query);
+        }
+    }
+
+    /**
+     * this should be called when the details panel close button is clicked
+     */
+    close(): void {
+        this.closed = true;
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/panel-theme.css b/web/gui2/src/main/webapp/app/fw/widget/panel-theme.css
new file mode 100644
index 0000000..6b984ab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/panel-theme.css
@@ -0,0 +1,66 @@
+/*
+ * 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 -- Panel Service (theme) -- CSS file
+ */
+
+.light .floatpanel {
+    background-color: white;
+    color: #3c3a3a;
+    border: 1px solid #c7c7c0;
+}
+
+.light .floatpanel hr {
+    border: 1px solid #c7c7c0;
+}
+
+.light .floatpanel .bottom tr:nth-child(odd) {
+    background-color: #f4f4f4;
+}
+
+.light .floatpanel .bottom tr:nth-child(even) {
+    background-color: #fbfbfb;
+}
+
+
+/* ========== DARK Theme ========== */
+
+.dark .floatpanel {
+    background-color: #282528;
+    color: #888c8c;
+    border: 1px solid #364144;
+}
+
+.dark .floatpanel th {
+    background-color: #242424;
+}
+
+.dark .floatpanel h2 {
+    color: #dddddd;
+}
+
+.dark .floatpanel hr {
+    border: 1px solid #30303a;
+}
+
+.dark .floatpanel .bottom tr:nth-child(odd) {
+    background-color: #333333;
+}
+
+.dark .floatpanel .bottom tr:nth-child(even) {
+    background-color: #3a3a3a;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/panel.base.ts b/web/gui2/src/main/webapp/app/fw/widget/panel.base.ts
new file mode 100644
index 0000000..703cbf9
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/panel.base.ts
@@ -0,0 +1,167 @@
+/*
+ * 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 } from '../util/fn.service';
+import { LoadingService } from '../layer/loading.service';
+import { LogService } from '../../log.service';
+import { WebSocketService } from '../remote/websocket.service';
+
+
+const noop = (): any => undefined;
+
+/**
+ ********* Static functions *********
+ */
+function margin(p) {
+    return p.settings.margin;
+}
+
+function hideMargin(p) {
+    return p.settings.hideMargin;
+}
+
+function noPx(p, what) {
+    return Number(p.el.style(what).replace(/px$/, ''));
+}
+
+function widthVal(p) {
+    return noPx(p, 'width');
+}
+
+function heightVal(p) {
+    return noPx(p, 'height');
+}
+
+function pxShow(p) {
+    return margin(p) + 'px';
+}
+
+function pxHide(p) {
+    return (-hideMargin(p) - widthVal(p) - (noPx(p, 'padding') * 2)) + 'px';
+}
+
+
+/**
+ * Base model of panel view - implemented by Panel components
+ */
+export interface PanelBase {
+    showPanel(cb: any): void;
+    hidePanel(cb: any): void;
+    togglePanel(cb: any): void;
+    emptyPanel(): void;
+    appendPanel(what: any): void;
+    panelWidth(w: number): number;
+    panelHeight(h: number): number;
+    panelBBox(): string;
+    panelIsVisible(): boolean;
+    classed(cls: any, bool: boolean): boolean;
+    panelEl(): any;
+}
+
+/**
+ * ONOS GUI -- Widget -- Panel Base class
+ *
+ * Replacing the panel service in the old implementation
+ */
+export abstract class PanelBaseImpl implements PanelBase {
+
+    protected on: boolean;
+    protected el: any;
+
+    constructor(
+        protected fs: FnService,
+        protected ls: LoadingService,
+        protected log: LogService,
+        protected wss: WebSocketService,
+        protected settings: any
+    ) {
+//        this.log.debug('Panel base class constructed');
+    }
+
+    showPanel(cb) {
+        const endCb = this.fs.isF(cb) || noop;
+        this.on = true;
+        this.el.transition().duration(this.settings.xtnTime)
+            .each('end', endCb)
+            .style(this.settings.edge, pxShow(this))
+            .style('opacity', 1);
+    }
+
+    hidePanel(cb) {
+        const endCb = this.fs.isF(cb) || noop;
+        const endOpacity = this.settings.fade ? 0 : 1;
+        this.on = false;
+        this.el.transition().duration(this.settings.xtnTime)
+            .each('end', endCb)
+            .style(this.settings.edge, pxHide(this))
+            .style('opacity', endOpacity);
+    }
+
+    togglePanel(cb): boolean {
+        if (this.on) {
+            this.hidePanel(cb);
+        } else {
+            this.showPanel(cb);
+        }
+        return this.on;
+    }
+
+    emptyPanel(): string {
+        return this.el.text('');
+    }
+
+    appendPanel(what) {
+        return this.el.append(what);
+    }
+
+    panelWidth(w: number): number {
+        if (w === undefined) {
+            return widthVal(this);
+        }
+        this.el.style('width', w + 'px');
+    }
+
+    panelHeight(h: number): number {
+        if (h === undefined) {
+            return heightVal(this);
+        }
+        this.el.style('height', h + 'px');
+    }
+
+    panelBBox(): string {
+        return this.el.node().getBoundingClientRect();
+    }
+
+    panelIsVisible(): boolean {
+        return this.on;
+    }
+
+    classed(cls, bool): boolean {
+        return this.el.classed(cls, bool);
+    }
+
+    panelEl() {
+        return this.el;
+    }
+
+
+    /**
+     * 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/src/main/webapp/app/fw/widget/panel.css b/web/gui2/src/main/webapp/app/fw/widget/panel.css
new file mode 100644
index 0000000..34d127f
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/panel.css
@@ -0,0 +1,61 @@
+/*
+ * 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 -- Panel Service (layout) -- CSS file
+ */
+
+.floatpanel {
+    position: absolute;
+    z-index: 100;
+    display: block;
+    top: 120px;
+    width: 500px;
+    right: -505px;
+    opacity: 100;
+
+    padding: 2px;
+    font-size: 10pt;
+}
+
+/* The following 4 are copied here from Theme until we sort out the
+ * theme service
+ */
+.floatpanel {
+    background-color: white;
+    color: #3c3a3a;
+    border: 1px solid #c7c7c0;
+}
+
+.floatpanel hr {
+    border: 1px solid #c7c7c0;
+}
+
+.floatpanel .bottom tr:nth-child(odd) {
+    background-color: #f4f4f4;
+}
+
+.floatpanel .bottom tr:nth-child(even) {
+    background-color: #fbfbfb;
+}
+
+.floatpanel.dialog {
+    top: 180px;
+}
+
+html[data-platform='iPad'] .floatpanel {
+    top: 80px;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tablebase.ts b/web/gui2/src/main/webapp/app/fw/widget/table.base.ts
similarity index 60%
rename from web/gui2/src/main/webapp/app/fw/widget/tablebase.ts
rename to web/gui2/src/main/webapp/app/fw/widget/table.base.ts
index 807a014..0093f72 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/tablebase.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/table.base.ts
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-present Open Networking Foundation
+ * 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.
@@ -13,39 +13,24 @@
  * 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 { LoadingService } from '../layer/loading.service';
 import { LogService } from '../../log.service';
 import { WebSocketService } from '../remote/websocket.service';
+import { Observable, of } from 'rxjs';
 
 const REFRESH_INTERVAL = 2000;
+const SEARCH_REGEX = '\\W';
 
 /**
- * Base model of table view - implemented by Table components
+ * Model of table annotations within this table base class
  */
-export interface TableBase {
-    annots: TableAnnots;
-    autoRefresh: boolean;
-    autoRefreshTip: string;
-    changedData: any;
-    payloadParams: any;
-    selId: string;
-    sortParams: any;
-    tableData: any[];
-    toggleRefresh(): void;
-    selectCallback(event: any, selRow: any): void;
-    parentSelCb(event: any, selRow: any): void;
-    sortCallback(): void;
-    responseCallback(): void;
-}
-
 interface TableAnnots {
     noRowsMsg: string;
 }
 
 /**
- * A model of data returned in a TableResponse
+ * A model of data returned from Web Socket in a TableResponse
  *
  * There is an interface extending from this one in the parent component
  */
@@ -56,23 +41,50 @@
 }
 
 /**
+ * A criteria for filtering the tableData
+ */
+export interface TableFilter {
+    queryStr: string;
+    queryBy: string;
+    sortBy: string;
+}
+
+/**
+ * Enumerated values for the sort dir
+ */
+export enum SortDir {
+    asc = 'asc', desc = 'desc'
+}
+
+/**
+ * A structure to format sort params for table
+ * This is sent to WebSocket as part of table request
+ */
+export interface SortParams {
+    firstCol: string;
+    firstDir: SortDir;
+    secondCol: string;
+    secondDir: SortDir;
+}
+
+/**
  * ONOS GUI -- Widget -- Table Base class
  */
-export class TableBaseImpl implements TableBase {
+export abstract class TableBaseImpl {
     // attributes from the interface
-    public annots: TableAnnots;
+    protected annots: TableAnnots;
+    protected changedData: string[] = [];
+    protected payloadParams: any;
+    protected sortParams: SortParams;
+    protected selectCallback; // Function
+    protected parentSelCb = null;
+    protected responseCallback; // Function
+    selId: string = undefined;
+    tableData: any[] = [];
+    tableDataFilter: TableFilter;
+    toggleRefresh; // Function
     autoRefresh: boolean = true;
     autoRefreshTip: string = 'Toggle auto refresh'; // TODO: get LION string
-    changedData: string[] = [];
-    payloadParams: any;
-    selId: string = undefined;
-    sortParams: any;
-    tableData: any[] = [];
-    toggleRefresh; // Function
-    selectCallback; // Function
-    parentSelCb = null;
-    sortCallback; // Function
-    responseCallback; // Function
 
     private root: string;
     private req: string;
@@ -87,19 +99,24 @@
         protected wss: WebSocketService,
         protected tag: string,
         protected idKey: string = 'id',
-        protected query: string = '',
         protected selCb = () => ({}) // Function
     ) {
         this.root = tag + 's';
         this.req = tag + 'DataRequest';
         this.resp = tag + 'DataResponse';
 
-        this.sortCallback = this.requestTableData;
         this.selectCallback = this.rowSelectionCb;
         this.toggleRefresh = () => {
             this.autoRefresh = !this.autoRefresh;
             this.autoRefresh ? this.startRefresh() : this.stopRefresh();
         };
+
+        // Mapped to the search and searchBy inputs in template
+        // Changes are handled through TableFilterPipe
+        this.tableDataFilter = <TableFilter>{
+            queryStr: '',
+            queryBy: '$',
+        };
     }
 
     init() {
@@ -115,8 +132,8 @@
         // Now send the WebSocket request and make it repeat every 2 seconds
         this.requestTableData();
         this.startRefresh();
-
-        this.log.debug('TableBase initialized');
+        this.log.debug('TableBase initialized. Calling ', this.req,
+            'every', REFRESH_INTERVAL, 'ms');
     }
 
     destroy() {
@@ -137,7 +154,7 @@
         const newTableData: any[] = Array.from(data[this.root]);
         this.annots.noRowsMsg = data.annots.no_rows_msg;
 
-        // If the onResp() function is set then call it
+        // If the parents onResp() function is set then call it
         if (this.responseCallback) {
             this.responseCallback(data);
         }
@@ -161,9 +178,12 @@
 
     /**
      * Table Data Request
+     * Pass in sort parameters and the set will be returned sorted
+     * In the old GUI there was also a query parameter, but this was not
+     * implemented on the server end
      */
     requestTableData() {
-        const p = Object.assign({}, this.sortParams, this.payloadParams, this.query);
+        const p = Object.assign({}, this.sortParams, this.payloadParams);
 
         // Allow it to sit in pending events
         if (this.wss.isConnected()) {
@@ -178,11 +198,11 @@
     /**
      * Row Selected
      */
-    rowSelectionCb(event: any, selRow: any) {
+    rowSelectionCb(event: any, selRow: any): void {
         const selId: string = selRow[this.idKey];
         this.selId = (this.selId === selId) ? undefined : selId;
+        this.log.debug('Row', selId, 'selected');
         if (this.parentSelCb) {
-            this.log.debug('Parent called on Row', selId, 'selected');
             this.parentSelCb(event, selRow);
         }
     }
@@ -212,4 +232,54 @@
     isChanged(id: string): boolean {
         return (this.fs.inArray(id, this.changedData) === -1) ? false : true;
     }
+
+    /**
+     * 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 + '%';
+    }
+
+    /**
+     * Change the sort order of the data returned
+     *
+     * sortParams are passed to the server by WebSocket and the data is
+     * returned sorted
+     *
+     * This is usually assigned to the (click) event on a column, and the column
+     * name passed in e.g. (click)="onSort('origin')
+     * If the column that is passed in is already the firstCol, then reverse its direction
+     * If a new column is passed in, then make the existing col the 2nd sort order
+     */
+    onSort(colName: string) {
+        if (this.sortParams.firstCol === colName) {
+            if (this.sortParams.firstDir === SortDir.desc) {
+                this.sortParams.firstDir = SortDir.asc;
+                return;
+            } else {
+                this.sortParams.firstDir = SortDir.desc;
+                return;
+            }
+        } else {
+            this.sortParams.secondCol = this.sortParams.firstCol;
+            this.sortParams.secondDir = this.sortParams.firstDir;
+            this.sortParams.firstCol = colName;
+            this.sortParams.firstDir = SortDir.desc;
+        }
+        this.log.debug('Sort params', this.sortParams);
+        this.requestTableData();
+    }
+
+    sortIcon(column: string): string {
+        if (this.sortParams.firstCol === column) {
+            if (this.sortParams.firstDir === SortDir.asc) {
+                return 'upArrow';
+            } else {
+                return 'downArrow';
+            }
+        } else {
+            return '';
+        }
+    }
 }
diff --git a/web/gui2/src/main/webapp/app/fw/widget/table-theme.css b/web/gui2/src/main/webapp/app/fw/widget/table.theme.css
similarity index 100%
rename from web/gui2/src/main/webapp/app/fw/widget/table-theme.css
rename to web/gui2/src/main/webapp/app/fw/widget/table.theme.css
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tabledetail.service.ts b/web/gui2/src/main/webapp/app/fw/widget/tabledetail.service.ts
deleted file mode 100644
index 76a5764..0000000
--- a/web/gui2/src/main/webapp/app/fw/widget/tabledetail.service.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2017-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';
-
-/**
- * ONOS GUI -- Widget -- Table Detail Panel Service
- */
-@Injectable()
-export class TableDetailService {
-
-  constructor(
-    private fs: FnService,
-    private log: LogService,
-  ) {
-    this.log.debug('TableDetailService constructed');
-  }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tablefilter.pipe.spec.ts b/web/gui2/src/main/webapp/app/fw/widget/tablefilter.pipe.spec.ts
new file mode 100644
index 0000000..8832feb
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/tablefilter.pipe.spec.ts
@@ -0,0 +1,104 @@
+/*
+ * 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 { TableFilterPipe } from './tablefilter.pipe';
+import { TableFilter } from './table.base';
+
+describe('TableFilterPipe', () => {
+
+    const pipe = new TableFilterPipe();
+    const items: any[] = new Array();
+    // Array item 0
+    items.push({
+        id: 'abc',
+        title: 'def',
+        origin: 'ghi'
+    });
+    // Array item 1
+    items.push({
+        id: 'pqr',
+        title: 'stu',
+        origin: 'vwx'
+    });
+    // Array item 2
+    items.push({
+        id: 'dog',
+        title: 'mouse',
+        origin: 'cat'
+    });
+
+
+    it('create an instance', () => {
+        expect(pipe).toBeTruthy();
+    });
+
+    it('expect it to handle empty search', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: '', queryBy: 'title'});
+        expect(filteredItems).toEqual(items);
+    });
+
+    it('expect it to handle empty items', () => {
+        const filteredItems: any[] =
+            pipe.transform(new Array(), <TableFilter>{queryStr: 'de', queryBy: 'title'});
+        expect(filteredItems).toEqual(new Array());
+    });
+
+
+    it('expect it to match 0 by title', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 'de', queryBy: 'title'});
+        expect(filteredItems).toEqual(items.slice(0, 1));
+    });
+
+    it('expect it to match 1 by title', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 'st', queryBy: 'title'});
+        expect(filteredItems).toEqual(items.slice(1, 2));
+    });
+
+    it('expect it to match 1 by uppercase title', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 'sT', queryBy: 'title'});
+        expect(filteredItems).toEqual(items.slice(1, 2));
+    });
+
+    it('expect it to not match by title', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 'pq', queryBy: 'title'});
+        expect(filteredItems.length).toEqual(0);
+    });
+
+    it('expect it to match 1 by all fields', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 'pq', queryBy: '$'});
+        expect(filteredItems).toEqual(items.slice(1, 2));
+    });
+
+    it('expect it to not match by all fields', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 'yz', queryBy: '$'});
+        expect(filteredItems.length).toEqual(0);
+    });
+
+    /**
+     * Check that items one and two contain a 't' - title=stu and origin=cat
+     */
+    it('expect it to match 1,2 by all fields', () => {
+        const filteredItems: any[] =
+            pipe.transform(items, <TableFilter>{queryStr: 't', queryBy: '$'});
+        expect(filteredItems).toEqual(items.slice(1));
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tablefilter.pipe.ts b/web/gui2/src/main/webapp/app/fw/widget/tablefilter.pipe.ts
new file mode 100644
index 0000000..5ef048c
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/tablefilter.pipe.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 { Pipe, PipeTransform } from '@angular/core';
+import { TableFilter } from './table.base';
+
+/**
+ * Only return the tabledata that matches filtering with some queries
+ *
+ * Note: the pipe is marked pure here as we need to filter on the
+ * content of the filter object (it's not a primitive type)
+ */
+@Pipe({
+  name: 'filter',
+  pure: false
+})
+export class TableFilterPipe implements PipeTransform {
+
+    /**
+     * From an array of table items just return those that match the filter
+     */
+    transform(items: any[], tableDataFilter: TableFilter): any[] {
+        if (!items) {
+            return [];
+        }
+        if (!tableDataFilter.queryStr) {
+            return items;
+        }
+
+        const queryStr = tableDataFilter.queryStr.toLowerCase();
+
+        return items.filter( it => {
+            if (tableDataFilter.queryBy === '$') {
+                const t1 = Object.values(it);
+                const t2 = Object.values(it).filter(value => {
+                               return (<string>value).toLowerCase().includes(queryStr);
+                           });
+                return Object.values(it).filter(value => {
+                    return (<string>value).toLowerCase().includes(queryStr);
+                }).length > 0;
+            } else {
+                return it[tableDataFilter.queryBy].toLowerCase().includes(queryStr);
+            }
+        });
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/toolbar.service.ts b/web/gui2/src/main/webapp/app/fw/widget/toolbar.service.ts
index 3681c80..6c1cb94 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/toolbar.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/toolbar.service.ts
@@ -19,7 +19,6 @@
 import { FnService } from '../util/fn.service';
 import { IconService } from '../svg/icon.service';
 import { LogService } from '../../log.service';
-import { PanelService } from '../layer/panel.service';
 
 /**
  * ONOS GUI -- Widget -- Toolbar Service
@@ -34,7 +33,6 @@
     private bns: ButtonService,
     private is: IconService,
     private log: LogService,
-    private ps: PanelService,
   ) {
     this.log.debug('ToolbarService constructed');
   }
diff --git a/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts b/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
index 50d013c..4a09032 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
@@ -21,11 +21,11 @@
 import { ButtonService } from './button.service';
 import { ChartBuilderService } from './chartbuilder.service';
 import { ListService } from './list.service';
-import { TableDetailService } from './tabledetail.service';
 import { ToolbarService } from './toolbar.service';
 import { SortableHeaderDirective } from './sortableheader.directive';
 import { TableResizeDirective } from './tableresize.directive';
 import { FlashChangesDirective } from './flashchanges.directive';
+import { TableFilterPipe } from './tablefilter.pipe';
 
 /**
  * ONOS GUI -- Widgets Module
@@ -39,14 +39,13 @@
   declarations: [
     SortableHeaderDirective,
     TableResizeDirective,
-    FlashChangesDirective
+    FlashChangesDirective,
+    TableFilterPipe
+  ],
+  exports: [
+    TableFilterPipe
   ],
   providers: [
-    ButtonService,
-    ChartBuilderService,
-    ListService,
-    TableDetailService,
-    ToolbarService
   ]
 })
 export class WidgetModule { }
diff --git a/web/gui2/src/main/webapp/app/onos-routing.module.ts b/web/gui2/src/main/webapp/app/onos-routing.module.ts
index abc6ee0..5d4af2b 100644
--- a/web/gui2/src/main/webapp/app/onos-routing.module.ts
+++ b/web/gui2/src/main/webapp/app/onos-routing.module.ts
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-present Open Networking Foundation
+ * 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.
@@ -16,6 +16,10 @@
 import { NgModule } from '@angular/core';
 import { Routes, RouterModule } from '@angular/router';
 
+/**
+ * The set of Routes in the application - can be chosen from nav menu or
+ * elsewhere like tabular icon for flows etc
+ */
 const onosRoutes: Routes = [
     {
         path: 'apps',
@@ -27,7 +31,7 @@
     },
     {
         path: '',
-        redirectTo: '',
+        redirectTo: 'devices', // Default to devices view - change to topo in future
         pathMatch: 'full'
     }
 ];
diff --git a/web/gui2/src/main/webapp/app/onos.component.css b/web/gui2/src/main/webapp/app/onos.component.css
index e57f958..6b7cd6c 100644
--- a/web/gui2/src/main/webapp/app/onos.component.css
+++ b/web/gui2/src/main/webapp/app/onos.component.css
@@ -19,7 +19,7 @@
  */
 
 #view {
-    padding: 6px;
+    padding: 0px;
     width: 100%;
     height: 100%;
 }
diff --git a/web/gui2/src/main/webapp/app/onos.component.html b/web/gui2/src/main/webapp/app/onos.component.html
index 3ec1c69..b71bab2 100644
--- a/web/gui2/src/main/webapp/app/onos.component.html
+++ b/web/gui2/src/main/webapp/app/onos.component.html
@@ -14,7 +14,7 @@
 ~ limitations under the License.
 -->
 <div id="view" onosDetectBrowser>
-    <onos-mast></onos-mast>
+    <onos-mast username="onos"></onos-mast>
     <onos-nav></onos-nav>
     <onos-veil #veil></onos-veil>
     <div>{{ wss.setVeilDelegate(veil) }}</div>
diff --git a/web/gui2/src/main/webapp/app/onos.component.ts b/web/gui2/src/main/webapp/app/onos.component.ts
index c2680b7..514fa91 100644
--- a/web/gui2/src/main/webapp/app/onos.component.ts
+++ b/web/gui2/src/main/webapp/app/onos.component.ts
@@ -13,13 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
+import { Observable, Subscription, fromEvent } from 'rxjs';
+import { map, filter } from 'rxjs/operators';
+
 import { LionService } from './fw/util/lion.service';
 import { LogService } from './log.service';
 import { KeyService } from './fw/util/key.service';
 import { ThemeService } from './fw/util/theme.service';
 import { GlyphService } from './fw/svg/glyph.service';
-import { PanelService } from './fw/layer/panel.service';
 import { QuickHelpService } from './fw/layer/quickhelp.service';
 import { EeService } from './fw/util/ee.service';
 import { WebSocketService, WsOptions } from './fw/remote/websocket.service';
@@ -62,8 +64,9 @@
   templateUrl: './onos.component.html',
   styleUrls: ['./onos.component.css', './onos.common.css']
 })
-export class OnosComponent implements OnInit, OnDestroy {
-    public title = 'onos';
+export class OnosComponent implements OnInit, AfterViewInit, OnDestroy {
+    private quickHelpSub: Subscription;
+    private quickHelpHandler: Observable<string>;
 
     // view ID to help page url map.. injected via the servlet
     viewMap: View[]  = [
@@ -80,7 +83,6 @@
         private ks: KeyService,
         private ts: ThemeService,
         private gs: GlyphService,
-        private ps: PanelService,
         private qhs: QuickHelpService,
         private ee: EeService,
         public wss: WebSocketService,
@@ -114,19 +116,62 @@
         this.log.debug('OnosComponent initialized');
     }
 
+    /**
+     * Start the listener for keystrokes for QuickHelp
+     *
+     * This creates an observable that listens to the '/','\' and Esc keystrokes
+     * anywhere in the web page - it strips the keyCode out of the keystroke
+     * and passes this to another observable that filters only for these keystrokes
+     * and finally maps these key code to text literals to drive the
+     * quick help feature
+     */
+    ngAfterViewInit() {
+        const keyStrokeHandler =
+            fromEvent(document, 'keyup').pipe(map((x: KeyboardEvent) => x.keyCode));
+        this.quickHelpHandler = keyStrokeHandler.pipe(
+            filter(x => {
+                return [27, 191, 220].includes(x);
+            })
+        ).pipe(
+            map(x => {
+                let direction;
+                switch (x) {
+                    case 27:
+                        direction = 'esc';
+                        break;
+                    case 191:
+                        direction = 'fwdslash';
+                        break;
+                    case 220:
+                        direction = 'backslash';
+                        break;
+                    default:
+                        direction = 'esc';
+                }
+                return direction;
+            })
+        );
+
+        // TODO: Make a Quick Help component popup
+        this.quickHelpSub = this.quickHelpHandler.subscribe((keyname) => {
+            this.log.debug('Keystroke', keyname);
+        });
+    }
+
     ngOnDestroy() {
         if (this.wss.isConnected()) {
             this.log.debug('Stopping Web Socket connection');
             this.wss.closeWebSocket();
         }
 
+        this.quickHelpSub.unsubscribe();
         this.log.debug('OnosComponent destroyed');
     }
 
     saucy(ee, ks) {
-        const map = ee.genMap(sauce);
-        Object.keys(map).forEach(function (k) {
-            ks.addSeq(k, map[k]);
+        const map1 = ee.genMap(sauce);
+        Object.keys(map1).forEach(function (k) {
+            ks.addSeq(k, map1[k]);
         });
     }
 }
diff --git a/web/gui2/src/main/webapp/tests/test.ts b/web/gui2/src/main/webapp/app/test.ts
similarity index 100%
rename from web/gui2/src/main/webapp/tests/test.ts
rename to web/gui2/src/main/webapp/app/test.ts
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps-routing.module.ts b/web/gui2/src/main/webapp/app/view/apps/apps-routing.module.ts
index 2fed90d..1d4f483 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps-routing.module.ts
+++ b/web/gui2/src/main/webapp/app/view/apps/apps-routing.module.ts
@@ -15,7 +15,7 @@
  */
 import { NgModule } from '@angular/core';
 import { Routes, RouterModule } from '@angular/router';
-import { AppsComponent } from './apps.component';
+import { AppsComponent } from './apps/apps.component';
 
 
 const appsRoutes: Routes = [
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps.component.html b/web/gui2/src/main/webapp/app/view/apps/apps.component.html
deleted file mode 100644
index c34694c..0000000
--- a/web/gui2/src/main/webapp/app/view/apps/apps.component.html
+++ /dev/null
@@ -1,115 +0,0 @@
-<!--
-~ Copyright 2014-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="ov-app" filedrop on-file-drop="appDropped()">
-    <div class="tabular-header">
-        <h2>
-            {{lionFn('title_apps')}}
-            ({{ tableData.length }}
-            {{ lionFn('total') }})
-        </h2>
-        <div class="ctrl-btns">
-            <div class="refresh" (click)="toggleRefresh()">
-                <onos-icon classes="{{ autoRefresh?'active refresh':'refresh' }}"
-                           iconId="refresh" iconSize="42" toolTip="{{ autoRefreshTip }}"></onos-icon>
-            </div>
-            <div class="separator"></div>
-
-            <!--<form id="inputFileForm">-->
-                <!--<input id="uploadFile"-->
-                       <!--type="file" size="50" accept=".oar,.jar"-->
-                       <!--file-model="appFile">-->
-            <!--</form>-->
-
-            <div class="active" trigger-form>
-                <onos-icon classes="{{ 'active upload' }}"
-                        iconId="upload" iconSize="42" toolTip="{{ uploadTip }}"></onos-icon>
-            </div>
-            <div (click)="appAction('activate')">
-                <onos-icon classes="{{ ctrlBtnState.installed?'active play':'play' }}"
-                           iconId="play" iconSize="42" toolTip="{{ activateTip }}"></onos-icon>
-            </div>
-            <div (click)="appAction('deactivate')">
-                <onos-icon classes="{{ ctrlBtnState.active?'active stop':'stop' }}"
-                        iconId="stop" iconSize="42" toolTip="{{ deactivateTip }}"></onos-icon>
-            </div>
-            <div (click)="appAction('uninstall')">
-                 <!--[ngClass]="{active: ctrlBtnState.selection}">-->
-                <!--tooltip tt-msg="uninstallTip"-->
-                <onos-icon classes="{{ ctrlBtnState.selection?'active garbage':'garbage' }}"
-                        iconId="garbage" iconSize="42" toolTip="{{ uninstallTip }}"></onos-icon>
-            </div>
-            <div (click)="downloadApp()">
-                <onos-icon classes="{{ ctrlBtnState.selection?'active download':'download' }}"
-                        iconId="download" iconSize="42" toolTip="{{ downloadTip }}"></onos-icon>
-            </div>
-        </div>
-
-        <!--<div class="search">-->
-            <!--<input type="text" ng-model="queryTxt" placeholder="Search"/>-->
-            <!--<select ng-model="queryBy">-->
-                <!--<option value="" disabled>Search By</option>-->
-                <!--<option value="$">All Fields</option>-->
-                <!--<option value="title">{{lionFn('title')}}</option>-->
-                <!--<option value="id">{{lionFn('app_id')}}</option>-->
-                <!--<option value="version">{{lionFn('version')}}</option>-->
-                <!--<option value="category">{{lionFn('category')}}</option>-->
-                <!--<option value="apporiginName">{{lionFn('origin')}}</option>-->
-
-            <!--</select>-->
-        <!--</div>-->
-
-
-    </div>
-
-    <div class="summary-list" onos-table-resize>
-        <table onos-flash-changes id-prop="id" width="100%">
-            <tr class="table-header">
-                <th colId="state" class="table-icon" sortable></th>
-                <th colId="icon" class="table-icon"></th>
-                <th colId="title" [ngClass]="{width: '340'}" sortable> {{lionFn('title')}} </th>
-                <th colId="id" [ngClass]="{width: '320px'}"sortable> {{lionFn('app_id')}} </th>
-                <th colId="version" [ngClass]="{width: '140px'}"sortable> {{lionFn('version')}} </th>
-                <th colId="category" [ngClass]="{width: '136px'}"sortable> {{lionFn('category')}} </th>
-                <th colId="origin" sortable> {{lionFn('origin')}} </th>
-            </tr>
-
-            <tr *ngIf="tableData.length === 0" class="no-data">
-                <td colspan="5">
-                    {{annots.no_rows_msg}}
-                </td>
-            </tr>
-            <!--&lt;!&ndash;TODO: Add back in  | filter:queryFilter&ndash;&gt;-->
-            <tr class="table-body" *ngFor="let app of tableData"
-                (click)="selectCallback($event, app)"
-                [ngClass]="{selected: app.id === selId, 'data-change': isChanged(app.id)}">
-                <td class="table-icon">
-                    <onos-icon iconId="{{app._iconid_state}}"></onos-icon>
-                </td>
-                <td class="table-icon">
-                    <!--<img data-ng-src="./rs/applications/{{app.icon}}/icon"-->
-                                            <!--height="24px" width="24px" />-->
-                </td>
-                <td>{{ app.title }}</td>
-                <td>{{ app.id }}</td>
-                <td>{{ app.version }}</td>
-                <td>{{ app.category }}</td>
-                <td>{{ app.origin }}</td>
-            </tr>
-        </table>
-
-    </div>
-
-</div>
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps.module.ts b/web/gui2/src/main/webapp/app/view/apps/apps.module.ts
index 48092a9..0f38dbc 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps.module.ts
+++ b/web/gui2/src/main/webapp/app/view/apps/apps.module.ts
@@ -15,10 +15,13 @@
  */
 import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
 import { AppsRoutingModule } from './apps-routing.module';
-import { AppsComponent } from './apps.component';
+import { AppsComponent } from './apps/apps.component';
+import { AppsDetailsComponent } from './appsdetails/appsdetails.component';
 import { TriggerFormDirective } from './triggerform.directive';
 import { SvgModule } from '../../fw/svg/svg.module';
+import { WidgetModule } from '../../fw/widget/widget.module';
 
 /**
  * ONOS GUI -- Apps View Module
@@ -31,10 +34,13 @@
     imports: [
         CommonModule,
         AppsRoutingModule,
-        SvgModule
+        SvgModule,
+        WidgetModule,
+        FormsModule
     ],
     declarations: [
         AppsComponent,
+        AppsDetailsComponent,
         TriggerFormDirective
     ]
 })
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css
new file mode 100644
index 0000000..5813f30
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.css
@@ -0,0 +1,53 @@
+/*
+ * 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 -- Applications View (layout) -- CSS file
+ */
+
+#ov-app h2 {
+    display: inline-block;
+}
+
+#ov-app div.ctrl-btns {
+    width: 290px;
+}
+
+/* -- Drag-n-Drop oar file upload -- */
+#ov-app form#inputFileForm,
+#ov-app input#uploadFile {
+    display: none;
+}
+
+.dropping {
+
+}
+
+/* -- Confirmation Dialog -- */
+#app-dialog {
+    top: 140px;
+    padding: 12px;
+}
+
+#app-dialog p {
+    font-size: 12pt;
+}
+
+#app-dialog p.strong {
+    font-weight: bold;
+    padding: 8px;
+    text-align: center;
+}
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html
new file mode 100644
index 0000000..1c4a22f
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.html
@@ -0,0 +1,137 @@
+<!--
+~ Copyright 2014-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="ov-app" filedrop on-file-drop="appDropped()">
+    <div class="tabular-header">
+        <h2>
+            {{lionFn('title_apps')}}
+            ({{ tableData.length }}
+            {{ lionFn('total') }})
+        </h2>
+        <div class="ctrl-btns">
+            <div class="refresh" (click)="toggleRefresh()">
+                <onos-icon classes="{{ autoRefresh?'active refresh':'refresh' }}"
+                           iconId="refresh" iconSize="42" toolTip="{{ autoRefreshTip }}"></onos-icon>
+            </div>
+            <div class="separator"></div>
+
+            <!--<form id="inputFileForm">-->
+                <!--<input id="uploadFile"-->
+                       <!--type="file" size="50" accept=".oar,.jar"-->
+                       <!--file-model="appFile">-->
+            <!--</form>-->
+
+            <div class="active" trigger-form>
+                <onos-icon classes="{{ 'active upload' }}"
+                        iconId="upload" iconSize="42" toolTip="{{ uploadTip }}"></onos-icon>
+            </div>
+            <div (click)="appAction('activate')">
+                <onos-icon classes="{{ ctrlBtnState.installed?'active play':'play' }}"
+                           iconId="play" iconSize="42" toolTip="{{ activateTip }}"></onos-icon>
+            </div>
+            <div (click)="appAction('deactivate')">
+                <onos-icon classes="{{ ctrlBtnState.active?'active stop':'stop' }}"
+                        iconId="stop" iconSize="42" toolTip="{{ deactivateTip }}"></onos-icon>
+            </div>
+            <div (click)="appAction('uninstall')">
+                 <!--[ngClass]="{active: ctrlBtnState.selection}">-->
+                <!--tooltip tt-msg="uninstallTip"-->
+                <onos-icon classes="{{ ctrlBtnState.selection?'active garbage':'garbage' }}"
+                        iconId="garbage" iconSize="42" toolTip="{{ uninstallTip }}"></onos-icon>
+            </div>
+            <div (click)="downloadApp()">
+                <onos-icon classes="{{ ctrlBtnState.selection?'active download':'download' }}"
+                        iconId="download" iconSize="42" toolTip="{{ downloadTip }}"></onos-icon>
+            </div>
+        </div>
+        <div class="search">
+            <input id="searchinput" [(ngModel)]="tableDataFilter.queryStr" type="search" #search placeholder="Search"/>
+                   <!--(keyup)="onSearch(search.value)" (search)="onSearch(search.value)"/>-->
+            <select [(ngModel)]="tableDataFilter.queryBy">
+                <!--(change)="onSearchBy($event)" (change)="search.value = ''">-->
+                <option value="" disabled>Search By</option>
+                <option value="$">All Fields</option>
+                <option value="title">{{lionFn('title')}}</option>
+                <option value="id">{{lionFn('app_id')}}</option>
+                <option value="version">{{lionFn('version')}}</option>
+                <option value="category">{{lionFn('category')}}</option>
+                <option value="origin">{{lionFn('origin')}}</option>
+            </select>
+        </div>
+
+    </div>
+
+    <div class="summary-list" onos-table-resize>
+        <table onos-flash-changes id-prop="id" width="100%">
+            <tr class="table-header">
+                <th colId="state" [ngStyle]="{width: '32px'}" class="table-icon" (click)="onSort('state')">
+                    <onos-icon classes="active" [iconId]="sortIcon('state')"></onos-icon>
+                </th>
+                <th colId="icon" [ngStyle]="{width: '32px'}" class="table-icon"></th>
+                <th colId="title"  (click)="onSort('title')">{{lionFn('title')}}
+                    <onos-icon classes="active" [iconId]="sortIcon('title')"></onos-icon>
+                </th>
+                <th colId="id" (click)="onSort('id')">{{lionFn('app_id')}}
+                    <onos-icon classes="active" [iconId]="sortIcon('id')"></onos-icon>
+                </th>
+                <th colId="version" (click)="onSort('version')"> {{lionFn('version')}}
+                    <onos-icon classes="active" [iconId]="sortIcon('version')"></onos-icon>
+                </th>
+                <th colId="category" (click)="onSort('category')"> {{lionFn('category')}}
+                    <onos-icon classes="active" [iconId]="sortIcon('category')"></onos-icon>
+                </th>
+                <th colId="origin" (click)="onSort('origin')"> {{lionFn('origin')}}
+                    <onos-icon classes="active" [iconId]="sortIcon('origin')"></onos-icon>
+                </th>
+            </tr>
+
+            <tr *ngIf="tableData.length === 0" class="no-data">
+                <td colspan="5">
+                    {{annots.no_rows_msg}}
+                </td>
+            </tr>
+            <!-- See https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
+                Angular has dropped the filter and order by pipe that were present in
+                AngularJS - filter and sort the data at source instead -->
+            <tr class="table-body" *ngFor="let app of tableData | filter: tableDataFilter"
+                (click)="selectCallback($event, app)"
+                [ngClass]="{selected: app.id === selId, 'data-change': isChanged(app.id)}">
+                <td class="table-icon">
+                    <onos-icon iconId="{{app._iconid_state}}"></onos-icon>
+                </td>
+                <td class="table-icon">
+                    <!-- The path below gets the app icon from the old GUI path -->
+                    <img src="../../ui/rs/applications/{{app.icon}}/icon"
+                                            height="24px" width="24px" />
+                </td>
+                <td>{{ app.title }}</td>
+                <td>{{ app.id }}</td>
+                <td>{{ app.version }}</td>
+                <td>{{ app.category }}</td>
+                <td>{{ app.origin }}</td>
+            </tr>
+        </table>
+
+    </div>
+    <!-- There are 2 ways this component can be included
+     1) Insert it in to the ngFor above and have it created as the row is rendered
+        for the row that has a selId != '' OR
+     2) Include it here and let selId dictate its state
+     The advantage in 2) is that panel can be animated in and out, as it is not
+     killed every time the selection changes.
+     -->
+    <onos-appsdetails  class="floatpanels" id="{{ selId }}"></onos-appsdetails>
+
+</div>
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts
new file mode 100644
index 0000000..1889a44
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.spec.ts
@@ -0,0 +1,177 @@
+/*
+ * 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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { FormsModule } from '@angular/forms';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+import { LogService } from '../../../log.service';
+import { AppsComponent } from './apps.component';
+import { AppsDetailsComponent } from '../appsdetails/appsdetails.component';
+import { DialogService } from '../../../fw/layer/dialog.service';
+import { FnService } from '../../../fw/util/fn.service';
+import { IconComponent } from '../../../fw/svg/icon/icon.component';
+import { IconService } from '../../../fw/svg/icon.service';
+import { KeyService } from '../../../fw/util/key.service';
+import { LionService } from '../../../fw/util/lion.service';
+import { LoadingService } from '../../../fw/layer/loading.service';
+import { ThemeService } from '../../../fw/util/theme.service';
+import { TableFilterPipe } from '../../../fw/widget/tablefilter.pipe';
+import { UrlFnService } from '../../../fw/remote/urlfn.service';
+import { WebSocketService } from '../../../fw/remote/websocket.service';
+import { of } from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+class MockDialogService {}
+
+class MockFnService {}
+
+class MockIconService {
+    loadIconDef() {}
+}
+
+class MockKeyService {}
+
+class MockLoadingService {
+    startAnim() {}
+    stop() {}
+    waiting() {}
+}
+
+class MockThemeService {}
+
+class MockUrlFnService {}
+
+class MockWebSocketService {
+    createWebSocket() {}
+    isConnected() { return false; }
+    unbindHandlers() {}
+    bindHandlers() {}
+}
+
+/**
+ * ONOS GUI -- Apps View -- Unit Tests
+ */
+describe('AppsComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: AppsComponent;
+    let fixture: ComponentFixture<AppsComponent>;
+    const bundleObj = {
+        'core.view.App': {
+            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, FormsModule ],
+            declarations: [ AppsComponent, IconComponent, AppsDetailsComponent, TableFilterPipe ],
+            providers: [
+                { provide: DialogService, useClass: MockDialogService },
+                { provide: FnService, useValue: fs },
+                { provide: IconService, useClass: MockIconService },
+                { provide: KeyService, useClass: MockKeyService },
+                { provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs: new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: LoadingService, useClass: MockLoadingService },
+                { provide: LogService, useValue: logSpy },
+                { provide: ThemeService, useClass: MockThemeService },
+                { provide: UrlFnService, useClass: MockUrlFnService },
+                { provide: WebSocketService, useClass: MockWebSocketService },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(AppsComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should have a div.tabular-header inside a div#ov-app', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#ov-app div.tabular-header'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a h2 inside the div.tabular-header', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#ov-app div.tabular-header h2'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual(' %title_apps% (0 %total%) ');
+    });
+
+    it('should have a refresh button inside the div.tabular-header', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#ov-app div.tabular-header div.ctrl-btns div.refresh'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have an active button inside the div.tabular-header', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#ov-app div.tabular-header div.ctrl-btns div.active'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a div.summary-list inside a div#ov-app', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div#ov-app div.summary-list'));
+        expect(divDe).toBeTruthy();
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps.component.ts b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts
similarity index 73%
rename from web/gui2/src/main/webapp/app/view/apps/apps.component.ts
rename to web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts
index ed6d30a..79fa310 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps.component.ts
+++ b/web/gui2/src/main/webapp/app/view/apps/apps/apps.component.ts
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
-import { DialogService } from '../../fw/layer/dialog.service';
-import { FnService } from '../../fw/util/fn.service';
-import { IconService } from '../../fw/svg/icon.service';
-import { KeyService } from '../../fw/util/key.service';
-import { LionService } from '../../fw/util/lion.service';
-import { LoadingService } from '../../fw/layer/loading.service';
-import { LogService } from '../../log.service';
-import { PanelService } from '../../fw/layer/panel.service';
-import { TableBaseImpl, TableResponse } from '../../fw/widget/tablebase';
-import { UrlFnService } from '../../fw/remote/urlfn.service';
-import { WebSocketService } from '../../fw/remote/websocket.service';
+import { DialogService } from '../../../fw/layer/dialog.service';
+import { FnService } from '../../../fw/util/fn.service';
+import { IconService } from '../../../fw/svg/icon.service';
+import { KeyService } from '../../../fw/util/key.service';
+import { LionService } from '../../../fw/util/lion.service';
+import { LoadingService } from '../../../fw/layer/loading.service';
+import { LogService } from '../../../log.service';
+import { TableBaseImpl, TableResponse, TableFilter, SortParams, SortDir } from '../../../fw/widget/table.base';
+import { UrlFnService } from '../../../fw/remote/urlfn.service';
+import { WebSocketService } from '../../../fw/remote/websocket.service';
+import { TableFilterPipe } from '../../../fw/widget/tablefilter.pipe';
 
 const INSTALLED = 'INSTALLED';
 const ACTIVE = 'ACTIVE';
@@ -36,8 +36,12 @@
 const detailsResp = 'appDetailsResponse';
 const fileUploadUrl = 'applications/upload';
 const activateOption = '?activate=true';
-const appUrlPrefix = 'rs/applications/';
-const iconUrlSuffix = '/icon';
+
+/** Prefix to access the REST service for applications */
+export const APPURLPREFIX = '../../ui/rs/applications/'; // TODO: This is a hack to work off GUIv1 URL
+/** Suffix to access the icon of the application - gives back an image */
+export const ICONURLSUFFIX = '/icon';
+
 const downloadSuffix = '/download';
 const dialogId = 'app-dialog';
 const dialogOpts = {
@@ -49,20 +53,26 @@
 };
 const propOrder = ['id', 'state', 'category', 'version', 'origin', 'role'];
 
+/**
+ * Model of the data returned through the Web Socket about apps.
+ */
 interface AppTableResponse extends TableResponse {
-    apps: Apps[];
+    apps: App[];
 }
 
-interface Apps {
+/**
+ * Model of the data returned through Web Socket for a single App
+ */
+export interface App {
     category: string;
     desc: string;
-    features: string;
+    features: string[];
     icon: string;
     id: string;
     origin: string;
-    permissions: string;
+    permissions: string[];
     readme: string;
-    required_apps: string;
+    required_apps: string[];
     role: string;
     state: string;
     title: string;
@@ -71,6 +81,9 @@
     _iconid_state: string;
 }
 
+/**
+ * Model of the Control Button
+ */
 interface CtrlBtnState {
     installed: boolean;
     selection: string;
@@ -85,7 +98,7 @@
   templateUrl: './apps.component.html',
   styleUrls: [
     './apps.component.css', './apps.theme.css',
-    '../../fw/widget/table.css', '../../fw/widget/table-theme.css'
+    '../../../fw/widget/table.css', '../../../fw/widget/table.theme.css'
     ]
 })
 export class AppsComponent extends TableBaseImpl implements OnInit, OnDestroy {
@@ -94,7 +107,6 @@
     lionFn; // Function
     warnDeactivate: string;
     warnOwnRisk: string;
-    friendlyProps: string[];
     ctrlBtnState: CtrlBtnState;
     detailsPanel: any;
     appFile: any;
@@ -114,43 +126,49 @@
         private lion: LionService,
         protected ls: LoadingService,
         protected log: LogService,
-        private ps: PanelService,
         private ufs: UrlFnService,
         protected wss: WebSocketService,
         @Inject('Window') private window: Window,
     ) {
         super(fs, null, log, wss, 'app');
         this.responseCallback = this.appResponseCb;
+        // pre-populate sort so active apps are at the top of the list
         this.sortParams = {
             firstCol: 'state',
-            firstDir: 'desc',
+            firstDir: SortDir.desc,
             secondCol: 'title',
-            secondDir: 'asc',
+            secondDir: SortDir.asc,
         };
-        // We want doLion() to be called only after the Lion service is populated (from the WebSocket)
-        this.lion.loadCb = (() => this.doLion());
+        // We want doLion() to be called only after the Lion
+        // service is populated (from the WebSocket)
+        // If lion is not ready we make do with a dummy function
+        // As soon a lion gets loaded this function will be replaced with
+        // the real thing
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('apps', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+
         this.ctrlBtnState = <CtrlBtnState>{
             installed: false,
             active: false
         };
-        if (this.lion.ubercache.length === 0) {
-            this.lionFn = this.dummyLion;
-        } else {
-            this.doLion();
-        }
-        this.uploadTip = this.lionFn('tt_ctl_upload');
-        this.activateTip = this.lionFn('tt_ctl_activate');
-        this.deactivateTip = this.lionFn('tt_ctl_deactivate');
-        this.uninstallTip = this.lionFn('tt_ctl_uninstall');
-        this.downloadTip = this.lionFn('tt_ctl_download');
     }
 
+    /**
+     * Initialize querying the WebSocket for App table details
+     */
     ngOnInit() {
         this.init();
-        this.log.debug('AppComponent initialized');
     }
 
+    /**
+     * Stop sending queries to WebSocket
+     */
     ngOnDestroy() {
+        this.lion.loadCbs.delete('apps');
         this.destroy();
         this.log.debug('AppComponent destroyed');
     }
@@ -199,8 +217,8 @@
             this.wss.sendEvent(appMgmtReq, {
                 action: action,
                 name: itemId,
-                sortCol: spar.sortCol,
-                sortDir: spar.sortDir,
+                sortCol: spar.firstCol,
+                sortDir: spar.firstDir,
             });
             if (action === 'uninstall') {
                 this.detailsPanel.hide();
@@ -229,13 +247,12 @@
 
     downloadApp() {
         if (this.ctrlBtnState.selection) {
-            (<any>this.window).location = appUrlPrefix + this.selId + downloadSuffix;
+            (<any>this.window).location = APPURLPREFIX + this.selId + ICONURLSUFFIX;
         }
     }
 
     /**
-     * Read the LION bundle for App - this should replace the dummyLion implementation
-     * of lionFn with a function from the LION Service
+     * Read the LION bundle for App and set up the lionFn
      */
     doLion() {
         this.lionFn = this.lion.bundle('core.view.App');
@@ -243,19 +260,11 @@
         this.warnDeactivate = this.lionFn('dlg_warn_deactivate');
         this.warnOwnRisk = this.lionFn('dlg_warn_own_risk');
 
-        this.friendlyProps = [
-            this.lionFn('app_id'), this.lionFn('state'),
-            this.lionFn('category'), this.lionFn('version'),
-            this.lionFn('origin'), this.lionFn('role'),
-        ];
-    }
-
-    /**
-     * 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 + '%';
+        this.uploadTip = this.lionFn('tt_ctl_upload');
+        this.activateTip = this.lionFn('tt_ctl_activate');
+        this.deactivateTip = this.lionFn('tt_ctl_deactivate');
+        this.uninstallTip = this.lionFn('tt_ctl_uninstall');
+        this.downloadTip = this.lionFn('tt_ctl_download');
     }
 
     appDropped() {
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps.theme.css b/web/gui2/src/main/webapp/app/view/apps/apps/apps.theme.css
similarity index 100%
rename from web/gui2/src/main/webapp/app/view/apps/apps.theme.css
rename to web/gui2/src/main/webapp/app/view/apps/apps/apps.theme.css
diff --git a/web/gui2/src/main/webapp/app/view/apps/apps.component.css b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css
similarity index 82%
rename from web/gui2/src/main/webapp/app/view/apps/apps.component.css
rename to web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css
index a1963c0..bfa8d08 100644
--- a/web/gui2/src/main/webapp/app/view/apps/apps.component.css
+++ b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.css
@@ -15,44 +15,8 @@
  */
 
 /*
- ONOS GUI -- Applications View (layout) -- CSS file
+ ONOS GUI -- Applications Details Panel (layout) -- CSS file
  */
-
-#ov-app h2 {
-    display: inline-block;
-}
-
-#ov-app div.ctrl-btns {
-    width: 290px;
-}
-
-/* -- Drag-n-Drop oar file upload -- */
-#ov-app form#inputFileForm,
-#ov-app input#uploadFile {
-    display: none;
-}
-
-.dropping {
-
-}
-
-/* -- Confirmation Dialog -- */
-#app-dialog {
-    top: 140px;
-    padding: 12px;
-}
-
-#app-dialog p {
-    font-size: 12pt;
-}
-
-#app-dialog p.strong {
-    font-weight: bold;
-    padding: 8px;
-    text-align: center;
-}
-
-/* -- Details Panel -- */
 #application-details-panel.floatpanel {
     z-index: 0;
 }
@@ -151,4 +115,3 @@
     padding: 6px 6px;
     text-align: left;
 }
-
diff --git a/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.html b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.html
new file mode 100644
index 0000000..a78bdd9
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.html
@@ -0,0 +1,99 @@
+<!--
+~ Copyright 2014-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="application-details-panel" class="floatpanel" [@appDetailsState]="id!=='' && !closed">
+    <!--<div *ngIf="visible" class="container">-->
+    <div class="container">
+        <div class="top">
+            <onos-icon class="close-btn" iconId="close" iconSize="20" (click)="close()"></onos-icon>
+            <div class="top-content">
+                <div class="app-title">{{ detailsData.title }}</div>
+                <div class="left app-icon">
+                    <img src="{{ iconUrl(id) }}">
+                </div>
+                <div class="right">
+                    <table class="app-props">
+                        <tbody>
+                            <tr>
+                                <td class="label">{{ lionFn('app_id') }}:</td>
+                                <td class="value">{{ id }}</td>
+                            </tr>
+                            <tr>
+                                <td class="label">{{ lionFn('state') }}:</td>
+                                <td class="value">{{ detailsData.state }}</td>
+                            </tr>
+                            <tr>
+                                <td class="label">{{ lionFn('category') }}:</td>
+                                <td class="value">{{ detailsData.category }}</td>
+                            </tr>
+                            <tr>
+                                <td class="label">{{ lionFn('version') }}:</td>
+                                <td class="value">{{ detailsData.version }}</td>
+                            </tr>
+                            <tr>
+                                <td class="label">{{ lionFn('origin') }}:</td>
+                                <td class="value">{{ detailsData.origin }}</td>
+                            </tr>
+                            <tr>
+                                <td class="label">{{ lionFn('role') }}:</td>
+                                <td class="value">{{ detailsData.role }}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+                <div class="app-url">
+                    <a href="{{ detailsData.url }}" target="_blank">{{ detailsData.url }}</a>
+                </div>
+            </div>
+        </div>
+        <hr>
+        <div class="middle">
+            <div class="app-readme">{{ detailsData.readme }}</div>
+        </div>
+        <hr>
+        <div class="bottom">
+            <h2>{{ lionFn('dp_features') }}</h2>
+            <div class="features">
+                <table>
+                    <tbody>
+                        <tr *ngFor="let feat of detailsData.features">
+                            <td>{{ feat }}</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <h2>{{ lionFn('dp_required_apps') }}</h2>
+            <div class="required-apps">
+                <table>
+                    <tbody>
+                        <tr *ngFor="let reqd of detailsData.required_apps">
+                            <td>{{ reqd }}</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <h2>{{ lionFn('dp_permissions') }}</h2>
+            <div class="permissions">
+                <table>
+                    <tbody>
+                        <tr *ngFor="let perm of detailsData.permissions">
+                            <td>{{ perm }}</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.spec.ts b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.spec.ts
new file mode 100644
index 0000000..f890cde
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.spec.ts
@@ -0,0 +1,172 @@
+/*
+ * 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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DebugElement } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+import { LogService } from '../../../log.service';
+import { AppsDetailsComponent } from './appsdetails.component';
+import { FnService } from '../../../../app/fw/util/fn.service';
+import { IconComponent } from '../../../../app/fw/svg/icon/icon.component';
+import { IconService } from '../../../../app/fw/svg/icon.service';
+import { LionService } from '../../../../app/fw/util/lion.service';
+import { UrlFnService } from '../../../fw/remote/urlfn.service';
+import { WebSocketService } from '../../../fw/remote/websocket.service';
+import { of } from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+class MockFnService {}
+
+class MockIconService {
+    loadIconDef() {}
+}
+
+class MockUrlFnService {}
+
+class MockWebSocketService {
+    createWebSocket() {}
+    isConnected() { return false; }
+    unbindHandlers() {}
+    bindHandlers() {}
+}
+
+/**
+ * ONOS GUI -- Apps Detail Panel View -- Unit Tests
+ */
+describe('AppsDetailsComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: AppsDetailsComponent;
+    let fixture: ComponentFixture<AppsDetailsComponent>;
+    const bundleObj = {
+        'core.view.App': {
+        }
+    };
+    const mockLion = (key) =>  {
+        return bundleObj[key] || '%' + key + '%';
+    };
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({'debug': 'panel'});
+
+        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: [ AppsDetailsComponent, IconComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: IconService, useClass: MockIconService },
+                { provide: LionService, useFactory: (() => {
+                        return {
+                            bundle: ((bundleId) => mockLion),
+                            ubercache: new Array(),
+                            loadCbs:  new Map<string, () => void>([])
+                        };
+                    })
+                },
+                { provide: LogService, useValue: logSpy },
+                { provide: UrlFnService, useClass: MockUrlFnService },
+                { provide: WebSocketService, useClass: MockWebSocketService },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(AppsDetailsComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should have an onos-icon.close-btn inside a div.top inside a div.container', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.container div.top onos-icon.close-btn'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a div.top-content inside a div.top inside a div.container', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.container div.top div.top-content'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a div.app-title inside a div.top-content', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.top-content div.app-title'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual('');
+    });
+
+    it('should have an img inside a div.left div.top-content', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.top-content div.left.app-icon img'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a table.app-props inside a div.right inside a div.top-content', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.top-content div.right table.app-props'));
+        const div: HTMLElement = divDe.nativeElement;
+        expect(div.textContent).toEqual('%app_id%:%state%:%category%:%version%:%origin%:%role%:');
+    });
+
+    it('should have an a inside an div.app-url inside a div.top-content', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.top-content div.app-url a'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a div.app-readme inside a div.middle inside a div.container', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.container div.middle div.app-readme'));
+        expect(divDe).toBeTruthy();
+    });
+
+    it('should have a div.features inside a div.bottom inside a div.container', () => {
+        const appDe: DebugElement = fixture.debugElement;
+        const divDe = appDe.query(By.css('div.container div.bottom div.features'));
+        expect(divDe).toBeTruthy();
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.ts b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.ts
new file mode 100644
index 0000000..1e6ed29
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/apps/appsdetails/appsdetails.component.ts
@@ -0,0 +1,119 @@
+/*
+ * 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, OnInit, OnDestroy, OnChanges } from '@angular/core';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+
+import { FnService } from '../../../fw/util/fn.service';
+import { LionService } from '../../../fw/util/lion.service';
+import { LoadingService } from '../../../fw/layer/loading.service';
+import { LogService } from '../../../log.service';
+import { WebSocketService } from '../../../fw/remote/websocket.service';
+
+import { DetailsPanelBaseImpl } from '../../../fw/widget/detailspanel.base';
+import { App, APPURLPREFIX, ICONURLSUFFIX } from '../apps/apps.component';
+
+/**
+ * The details view when an app is clicked from the apps view
+ *
+ * This is expected to be passed an 'id' and it makes a call
+ * to the WebSocket with an appDetailsRequest, and gets back an
+ * appDetailsResponse.
+ *
+ * The animated fly-in is controlled by the animation below
+ * The appDetailsState is attached to application-details-panel
+ * and is false (flies out) when id='' and true (flies in) when
+ * id has a value
+ */
+@Component({
+  selector: 'onos-appsdetails',
+  templateUrl: './appsdetails.component.html',
+  styleUrls: [
+    './appsdetails.component.css',
+    '../../../fw/widget/panel.css', '../../../fw/widget/panel-theme.css'
+  ],
+  animations: [
+    trigger('appDetailsState', [
+      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 AppsDetailsComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy, OnChanges {
+    @Input() id: string;
+
+    lionFn; // Function
+    constructor(
+        protected fs: FnService,
+        protected ls: LoadingService,
+        protected log: LogService,
+        protected wss: WebSocketService,
+        protected lion: LionService,
+    ) {
+        super(fs, ls, log, wss, 'app');
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('appsdetails', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+    }
+
+    /**
+     * There is a possibility that a previous selection
+     * is already registered for call - if so wait 100ms
+     * for it to deregister - this is because in the list of
+     * apps we might have selected one higher up the list and
+     * it is now being processed here before an older selection
+     * farther down the list has been removed
+     */
+    ngOnInit() {
+        this.init();
+        this.log.debug('App Details Component initialized:', this.id);
+    }
+
+    /**
+     * Stop listening to appDetailsResponse on WebSocket
+     */
+    ngOnDestroy() {
+        this.lion.loadCbs.delete('appsdetails');
+        this.destroy();
+        this.log.debug('App Details Component destroyed');
+    }
+
+    ngOnChanges() {
+        this.requestDetailsPanelData(this.id);
+    }
+
+    iconUrl(appId: string): string {
+        return APPURLPREFIX + appId + ICONURLSUFFIX;
+    }
+
+    /**
+     * Read the LION bundle for App and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.App');
+    }
+
+}
diff --git a/web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts b/web/gui2/src/main/webapp/app/view/device/device.component.spec.ts
similarity index 74%
rename from web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts
rename to web/gui2/src/main/webapp/app/view/device/device.component.spec.ts
index 40edf5d..066a636 100644
--- a/web/gui2/src/main/webapp/tests/app/view/device/device.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/device/device.component.spec.ts
@@ -17,23 +17,20 @@
 import { ActivatedRoute, Params } from '@angular/router';
 import { DebugElement } from '@angular/core';
 import { By } from '@angular/platform-browser';
-import { LogService } from '../../../../app/log.service';
-import { DeviceComponent } from '../../../../app/view/device/device.component';
+import { LogService } from '../../log.service';
+import { DeviceComponent } from './device.component';
 
-import { DetailsPanelService } from '../../../../app/fw/layer/detailspanel.service';
-import { FnService, WindowSize } from '../../../../app/fw/util/fn.service';
-import { IconService } from '../../../../app/fw/svg/icon.service';
-import { GlyphService } from '../../../../app/fw/svg/glyph.service';
-import { IconComponent } from '../../../../app/fw/svg/icon/icon.component';
-import { KeyService } from '../../../../app/fw/util/key.service';
-import { LoadingService } from '../../../../app/fw/layer/loading.service';
-import { NavService } from '../../../../app/fw/nav/nav.service';
-import { MastService } from '../../../../app/fw/mast/mast.service';
-import { PanelService } from '../../../../app/fw/layer/panel.service';
-import { SvgUtilService } from '../../../../app/fw/svg/svgutil.service';
-import { TableDetailService } from '../../../../app/fw/widget/tabledetail.service';
-import { ThemeService } from '../../../../app/fw/util/theme.service';
-import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
+import { FnService, WindowSize } from '../../fw/util/fn.service';
+import { IconService } from '../../fw/svg/icon.service';
+import { GlyphService } from '../../fw/svg/glyph.service';
+import { IconComponent } from '../../fw/svg/icon/icon.component';
+import { KeyService } from '../../fw/util/key.service';
+import { LoadingService } from '../../fw/layer/loading.service';
+import { NavService } from '../../fw/nav/nav.service';
+import { MastService } from '../../fw/mast/mast.service';
+import { SvgUtilService } from '../../fw/svg/svgutil.service';
+import { ThemeService } from '../../fw/util/theme.service';
+import { WebSocketService } from '../../fw/remote/websocket.service';
 import { of } from 'rxjs';
 
 class MockActivatedRoute extends ActivatedRoute {
@@ -64,8 +61,6 @@
 
 class MockMastService {}
 
-class MockPanelService {}
-
 class MockTableBuilderService {}
 
 class MockTableDetailService {}
@@ -107,11 +102,9 @@
         };
         fs = new FnService(ar, logSpy, windowMock);
 
-
         TestBed.configureTestingModule({
             declarations: [ DeviceComponent, IconComponent ],
             providers: [
-                { provide: DetailsPanelService, useClass: MockDetailsPanelService },
                 { provide: FnService, useValue: fs },
                 { provide: IconService, useClass: MockIconService },
                 { provide: GlyphService, useClass: MockGlyphService },
@@ -120,8 +113,6 @@
                 { provide: MastService, useClass: MockMastService },
                 { provide: NavService, useClass: MockNavService },
                 { provide: LogService, useValue: logSpy },
-                { provide: PanelService, useClass: MockPanelService },
-                { provide: TableDetailService, useClass: MockTableDetailService },
                 { provide: ThemeService, useClass: MockThemeService },
                 { provide: WebSocketService, useClass: MockWebSocketService },
                 { provide: 'Window', useValue: windowMock },
diff --git a/web/gui2/src/main/webapp/app/view/device/device.component.ts b/web/gui2/src/main/webapp/app/view/device/device.component.ts
index 99564d0..ecccc34 100644
--- a/web/gui2/src/main/webapp/app/view/device/device.component.ts
+++ b/web/gui2/src/main/webapp/app/view/device/device.component.ts
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
-import { DetailsPanelService } from '../../fw/layer/detailspanel.service';
 import { FnService } from '../../fw/util/fn.service';
 import { IconService } from '../../fw/svg/icon.service';
 import { KeyService } from '../../fw/util/key.service';
@@ -22,15 +21,19 @@
 import { LogService } from '../../log.service';
 import { MastService } from '../../fw/mast/mast.service';
 import { NavService } from '../../fw/nav/nav.service';
-import { PanelService } from '../../fw/layer/panel.service';
-import { TableBaseImpl, TableResponse } from '../../fw/widget/tablebase';
-import { TableDetailService } from '../../fw/widget/tabledetail.service';
+import { TableBaseImpl, TableResponse } from '../../fw/widget/table.base';
 import { WebSocketService } from '../../fw/remote/websocket.service';
 
+/**
+ * Model of the response from WebSocket
+ */
 interface DeviceTableResponse extends TableResponse {
     devices: Device[];
 }
 
+/**
+ * Model of the devices returned from the WebSocket
+ */
 interface Device {
     available: boolean;
     chassisid: string;
@@ -54,7 +57,7 @@
 @Component({
   selector: 'onos-device',
   templateUrl: './device.component.html',
-  styleUrls: ['./device.component.css', './device.theme.css', '../../fw/widget/table.css', '../../fw/widget/table-theme.css']
+  styleUrls: ['./device.component.css', './device.theme.css', '../../fw/widget/table.css', '../../fw/widget/table.theme.css']
 })
 export class DeviceComponent extends TableBaseImpl implements OnInit, OnDestroy {
 
@@ -66,7 +69,6 @@
     pipeconfTip = 'Show pipeconf view for selected device';
 
     constructor(
-        private dps: DetailsPanelService,
         protected fs: FnService,
         protected ls: LoadingService,
         private is: IconService,
@@ -74,8 +76,6 @@
         protected log: LogService,
         private mast: MastService,
         private nav: NavService,
-        private ps: PanelService,
-        private tds: TableDetailService,
         protected wss: WebSocketService,
         @Inject('Window') private window: Window,
     ) {
diff --git a/web/gui2/src/main/webapp/tests/app/fw/layer/detailspanel.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/layer/detailspanel.service.spec.ts
deleted file mode 100644
index 80f8d5a..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/layer/detailspanel.service.spec.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.
- */
-import { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { DetailsPanelService } from '../../../../app/fw/layer/detailspanel.service';
-import { EditableTextService } from '../../../../app/fw/layer/editabletext.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-import { IconService } from '../../../../app/fw/svg/icon.service';
-import { MastService } from '../../../../app/fw/mast/mast.service';
-import { PanelService } from '../../../../app/fw/layer/panel.service';
-import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
-
-class MockEditableTextService {}
-
-class MockIconService {}
-
-class MockFnService {}
-
-class MockMastService {}
-
-class MockPanelService {}
-
-class MockWebSocketService {}
-
-/**
- * ONOS GUI -- Layer -- Details Panel Service - Unit Tests
- */
-describe('DetailsPanelService', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [DetailsPanelService,
-                { provide: EditableTextService, useClass: MockEditableTextService },
-                { provide: FnService, useClass: MockFnService },
-                { provide: IconService, useClass: MockIconService },
-                { provide: LogService, useValue: log },
-                { provide: MastService, useClass: MockMastService },
-                { provide: PanelService, useClass: MockPanelService },
-                { provide: WebSocketService, useClass: MockWebSocketService },
-            ]
-        });
-    });
-
-    it('should be created', inject([DetailsPanelService], (service: DetailsPanelService) => {
-        expect(service).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts
index 66d3b85..f51fb0e 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/fw/layer/dialog.service.spec.ts
@@ -20,14 +20,11 @@
 import { DialogService } from '../../../../app/fw/layer/dialog.service';
 import { FnService } from '../../../../app/fw/util/fn.service';
 import { KeyService } from '../../../../app/fw/util/key.service';
-import { PanelService } from '../../../../app/fw/layer/panel.service';
 
 class MockFnService {}
 
 class MockKeyService {}
 
-class MockPanelService {}
-
 /**
  * ONOS GUI -- Layer -- Dialog Service - Unit Tests
  */
@@ -42,7 +39,6 @@
                 { provide: LogService, useValue: log },
                 { provide: FnService, useClass: MockFnService },
                 { provide: KeyService, useClass: MockKeyService },
-                { provide: PanelService, useClass: MockPanelService },
             ]
         });
     });
diff --git a/web/gui2/src/main/webapp/tests/app/fw/layer/panel.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/layer/panel.service.spec.ts
deleted file mode 100644
index 4ba7f69..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/layer/panel.service.spec.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-import { TestBed, inject } from '@angular/core/testing';
-
-import { PanelService } from '../../../../app/fw/layer/panel.service';
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-
-class MockFnService {}
-
-/**
- * ONOS GUI -- Layer -- Panel Service - Unit Tests
- */
-describe('PanelService', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [PanelService,
-                { provide: LogService, useValue: log },
-                { provide: FnService, useClass: MockFnService },
-            ]
-        });
-    });
-
-    it('should be created', inject([PanelService], (service: PanelService) => {
-        expect(service).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/mast/mast/mast.component.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/mast/mast/mast.component.spec.ts
deleted file mode 100644
index fe97527..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/mast/mast/mast.component.spec.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.
- */
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { LogService } from '../../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../../app/consolelogger.service';
-import { MastComponent } from '../../../../../app/fw/mast/mast/mast.component';
-import { IconComponent } from '../../../../../app/fw/svg/icon/icon.component';
-import { DialogService } from '../../../../../app/fw/layer/dialog.service';
-import { LionService } from '../../../../../app/fw/util/lion.service';
-import { IconService } from '../../../../../app/fw/svg/icon.service';
-import { NavService } from '../../../../../app/fw/nav/nav.service';
-import { WebSocketService } from '../../../../../app/fw/remote/websocket.service';
-
-class MockDialogService {}
-
-class MockLionService {}
-
-class MockNavService {}
-
-class MockWebSocketService {}
-
-class MockIconService {}
-
-/**
- * ONOS GUI -- Masthead Controller - Unit Tests
- */
-describe('MastComponent', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-        TestBed.configureTestingModule({
-            declarations: [ MastComponent, IconComponent ],
-            providers: [
-                { provide: DialogService, useClass: MockDialogService },
-                { provide: LionService, useClass: MockLionService },
-                { provide: LogService, useValue: log },
-                { provide: NavService, useClass: MockNavService },
-                { provide: WebSocketService, useClass: MockWebSocketService },
-                { provide: IconService, useClass: MockIconService },
-            ]
-        });
-    });
-
-    it('should create', () => {
-        const fixture = TestBed.createComponent(MastComponent);
-        const component = fixture.componentInstance;
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/nav/nav/nav.component.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/nav/nav/nav.component.spec.ts
deleted file mode 100644
index 8d86c80..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/nav/nav/nav.component.spec.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { LogService } from '../../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../../app/consolelogger.service';
-import { NavComponent } from '../../../../../app/fw/nav/nav/nav.component';
-import { IconComponent } from '../../../../../app/fw/svg/icon/icon.component';
-import { IconService } from '../../../../../app/fw/svg/icon.service';
-import { NavService } from '../../../../../app/fw/nav/nav.service';
-
-class MockNavService {}
-
-class MockIconService {}
-
-/**
- * ONOS GUI -- Util -- Navigation Component - Unit Tests
- */
-describe('NavComponent', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-        TestBed.configureTestingModule({
-            declarations: [ NavComponent, IconComponent ],
-            providers: [
-                { provide: LogService, useValue: log },
-                { provide: IconService, useClass: MockIconService },
-                { provide: NavService, useClass: MockNavService },
-            ]
-        });
-    });
-
-    it('should create', () => {
-        const fixture = TestBed.createComponent(NavComponent);
-        const component = fixture.componentInstance;
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/widget/tabledetail.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/widget/tabledetail.service.spec.ts
deleted file mode 100644
index 8854413e..0000000
--- a/web/gui2/src/main/webapp/tests/app/fw/widget/tabledetail.service.spec.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-import { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { TableDetailService } from '../../../../app/fw/widget/tabledetail.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-
-class MockFnService {}
-
-/**
- * ONOS GUI -- Widget -- Table Detail Service - Unit Tests
- */
-describe('TableDetailService', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [TableDetailService,
-                { provide: LogService, useValue: log },
-                { provide: FnService, useClass: MockFnService },
-            ]
-        });
-    });
-
-    it('should be created', inject([TableDetailService], (service: TableDetailService) => {
-        expect(service).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts b/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts
index 522586f..cfc49e7 100644
--- a/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/fw/widget/toolbar.service.spec.ts
@@ -21,7 +21,6 @@
 import { ButtonService } from '../../../../app/fw/widget/button.service';
 import { FnService } from '../../../../app/fw/util/fn.service';
 import { IconService } from '../../../../app/fw/svg/icon.service';
-import { PanelService } from '../../../../app/fw/layer/panel.service';
 
 class MockButtonService {}
 
@@ -29,8 +28,6 @@
 
 class MockFnService {}
 
-class MockPanelService {}
-
 /**
  * ONOS GUI -- Widget -- Toolbar Service - Unit Tests
  */
@@ -46,7 +43,6 @@
                 { provide: ButtonService, useClass: MockButtonService },
                 { provide: IconService, useClass: MockIconService },
                 { provide: FnService, useClass: MockFnService },
-                { provide: PanelService, useClass: MockPanelService },
             ]
         });
     });
diff --git a/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts b/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts
index 8dcb9e0..c03f680 100644
--- a/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts
+++ b/web/gui2/src/main/webapp/tests/app/onos.component.spec.ts
@@ -35,7 +35,6 @@
 import { LionService } from '../../app/fw/util/lion.service';
 import { NavService } from '../../app/fw/nav/nav.service';
 import { OnosService } from '../../app/onos.service';
-import { PanelService } from '../../app/fw/layer/panel.service';
 import { QuickHelpService } from '../../app/fw/layer/quickhelp.service';
 import { SvgUtilService } from '../../app/fw/svg/svgutil.service';
 import { ThemeService } from '../../app/fw/util/theme.service';
@@ -65,8 +64,6 @@
 
 class MockOnosService {}
 
-class MockPanelService {}
-
 class MockQuickHelpService {}
 
 class MockSpriteService {}
@@ -131,7 +128,6 @@
                 { provide: NavService, useClass: MockNavService },
                 { provide: OnosService, useClass: MockOnosService },
                 { provide: QuickHelpService, useClass: MockQuickHelpService },
-                { provide: PanelService, useClass: MockPanelService },
                 { provide: SpriteService, useClass: MockSpriteService },
                 { provide: ThemeService, useClass: MockThemeService },
                 { provide: WebSocketService, useClass: MockWebSocketService },
diff --git a/web/gui2/src/main/webapp/tests/app/view/apps/apps.component.spec.ts b/web/gui2/src/main/webapp/tests/app/view/apps/apps.component.spec.ts
deleted file mode 100644
index 2d2f75f..0000000
--- a/web/gui2/src/main/webapp/tests/app/view/apps/apps.component.spec.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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 { LogService } from '../../../../app/log.service';
-import { AppsComponent } from '../../../../app/view/apps/apps.component';
-import { DialogService } from '../../../../app/fw/layer/dialog.service';
-import { FnService } from '../../../../app/fw/util/fn.service';
-import { IconComponent } from '../../../../app/fw/svg/icon/icon.component';
-import { IconService } from '../../../../app/fw/svg/icon.service';
-import { KeyService } from '../../../../app/fw/util/key.service';
-import { LionService } from '../../../../app/fw/util/lion.service';
-import { LoadingService } from '../../../../app/fw/layer/loading.service';
-import { PanelService } from '../../../../app/fw/layer/panel.service';
-import { ThemeService } from '../../../../app/fw/util/theme.service';
-import { UrlFnService } from '../../../../app/fw/remote/urlfn.service';
-import { WebSocketService } from '../../../../app/fw/remote/websocket.service';
-import { of } from 'rxjs';
-
-class MockActivatedRoute extends ActivatedRoute {
-    constructor(params: Params) {
-        super();
-        this.queryParams = of(params);
-    }
-}
-
-class MockDialogService {}
-
-class MockFnService {}
-
-class MockIconService {
-    loadIconDef() {}
-}
-
-class MockKeyService {}
-
-class MockLoadingService {
-    startAnim() {}
-    stop() {}
-    waiting() {}
-}
-
-class MockPanelService {}
-
-class MockTableBuilderService {}
-
-class MockThemeService {}
-
-class MockUrlFnService {}
-
-class MockWebSocketService {
-    createWebSocket() {}
-    isConnected() { return false; }
-    unbindHandlers() {}
-    bindHandlers() {}
-}
-
-/**
- * ONOS GUI -- Apps View -- Unit Tests
- */
-describe('AppsComponent', () => {
-    let fs: FnService;
-    let ar: MockActivatedRoute;
-    let windowMock: Window;
-    let logServiceSpy: jasmine.SpyObj<LogService>;
-    let component: AppsComponent;
-    let fixture: ComponentFixture<AppsComponent>;
-    const bundleObj = {
-        'core.view.App': {
-            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({
-            declarations: [ AppsComponent, IconComponent ],
-            providers: [
-                { provide: DialogService, useClass: MockDialogService },
-                { provide: FnService, useValue: fs },
-                { provide: IconService, useClass: MockIconService },
-                { provide: KeyService, useClass: MockKeyService },
-                { provide: LionService, useFactory: (() => {
-                        return {
-                            bundle: ((bundleId) => mockLion),
-                            ubercache: new Array()
-                        };
-                    })
-                },
-                { provide: LoadingService, useClass: MockLoadingService },
-                { provide: LogService, useValue: logSpy },
-                { provide: PanelService, useClass: MockPanelService },
-                { provide: ThemeService, useClass: MockThemeService },
-                { provide: UrlFnService, useClass: MockUrlFnService },
-                { provide: WebSocketService, useClass: MockWebSocketService },
-                { provide: 'Window', useValue: windowMock },
-            ]
-        })
-        .compileComponents();
-        logServiceSpy = TestBed.get(LogService);
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(AppsComponent);
-        component = fixture.debugElement.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/web/gui2/src/main/webapp/tests/app/view/device/devicedetailspanel.directive.spec.ts b/web/gui2/src/main/webapp/tests/app/view/device/devicedetailspanel.directive.spec.ts
deleted file mode 100644
index 883d38a..0000000
--- a/web/gui2/src/main/webapp/tests/app/view/device/devicedetailspanel.directive.spec.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-import { TestBed, inject } from '@angular/core/testing';
-
-import { LogService } from '../../../../app/log.service';
-import { ConsoleLoggerService } from '../../../../app/consolelogger.service';
-import { DeviceDetailsPanelDirective } from '../../../../app/view/device/devicedetailspanel.directive';
-import { KeyService } from '../../../../app/fw/util/key.service';
-
-class MockKeyService {}
-
-/**
- * ONOS GUI -- Device View Module - Unit Tests
- */
-describe('DeviceDetailsPanelDirective', () => {
-    let log: LogService;
-
-    beforeEach(() => {
-        log = new ConsoleLoggerService();
-
-        TestBed.configureTestingModule({
-            providers: [ DeviceDetailsPanelDirective,
-                { provide: LogService, useValue: log },
-                { provide: KeyService, useClass: MockKeyService }
-            ]
-        });
-    });
-
-    afterEach(() => {
-        log = null;
-    });
-
-    it('should create an instance', inject([DeviceDetailsPanelDirective], (directive: DeviceDetailsPanelDirective) => {
-        expect(directive).toBeTruthy();
-    }));
-});
diff --git a/web/gui2/src/main/webapp/tsconfig.spec.json b/web/gui2/src/main/webapp/tsconfig.spec.json
index a322b0e..670b8aa 100644
--- a/web/gui2/src/main/webapp/tsconfig.spec.json
+++ b/web/gui2/src/main/webapp/tsconfig.spec.json
@@ -10,7 +10,7 @@
     ]
   },
   "files": [
-    "tests/test.ts",
+    "app/test.ts",
     "polyfills.ts"
   ],
   "include": [
diff --git a/web/gui2/summary.json b/web/gui2/summary.json
new file mode 100644
index 0000000..4debb61
--- /dev/null
+++ b/web/gui2/summary.json
@@ -0,0 +1,6 @@
+[
+  {
+    "title":"Angular Migration",
+    "file": "AngularMigration.md"
+  }
+]