You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2019/04/26 10:18:45 UTC

[ignite] 08/17: WC-902 Web Console: Re-implemented "Profile" page on Angular.

This is an automated email from the ASF dual-hosted git repository.

amashenkov pushed a commit to branch gg-17462
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit e5673e450bdfb8b1ca38e4bfc74753ff67356dff
Author: Ilya Borisov <ib...@gridgain.com>
AuthorDate: Wed Apr 24 15:37:41 2019 +0700

    WC-902 Web Console: Re-implemented "Profile" page on Angular.
---
 .../e2e/testcafe/components/FormField.js           |   13 +
 .../e2e/testcafe/components/PanelCollapsible.js    |   10 +
 .../e2e/testcafe/fixtures/user-profile/profile.js  |    3 +-
 modules/web-console/e2e/testcafe/package.json      |   12 +-
 .../e2e/testcafe/page-models/pageProfile.js        |   26 +-
 modules/web-console/frontend/.babelrc              |    1 +
 modules/web-console/frontend/.eslintrc             |    3 +-
 .../components/copyToClipboardButton.component.ts  |   64 +
 .../components/form-field/autofocus.directive.ts}  |   19 +-
 .../components/form-field/error.component.ts}      |   29 +-
 .../components/form-field/errorStyles.provider.ts} |   14 +-
 .../components/form-field/errors.component.ts      |   79 +
 .../components/form-field/formField.component.scss |  175 +++
 .../components/form-field/formField.component.ts   |  125 ++
 .../components/form-field/hint.component.ts}       |   28 +-
 .../app-angular/components/form-field/index.ts     |   36 +
 .../passwordVisibilityToggleButton.component.ts    |   62 +
 .../form-field/scrollToFirstInvalid.directive.ts   |   61 +
 .../components/form-field/tooltip.component.ts}    |   45 +-
 .../form-field/validationMessages.provider.ts}     |   23 +-
 .../app-angular/components/igniteIcon.component.ts |   55 +
 .../components/page-profile/component.ts           |  150 ++
 .../components/page-profile/style.scss             |   28 +-
 .../components/page-profile/template.html          |   93 ++
 .../components/panelCollapsible.component.ts       |  152 ++
 .../components/serviceBootstrap.ts}                |   28 +-
 .../web-console/frontend/app-angular/downgrade.ts  |   37 +
 modules/web-console/frontend/app-angular/index.ts  |  116 ++
 .../style.scss => app-angular/states.ts}           |   27 +-
 .../web-console/frontend/app-angular/style.scss    |   66 +
 .../style.scss => app-angular/upgrade.ts}          |   27 +-
 .../web-console/frontend/app/{app.js => app.ts}    |   13 +-
 .../form-field/showValidationError.directive.ts    |    3 +-
 .../app/components/page-profile/controller.js      |   97 --
 .../app/components/page-profile/template.pug       |  161 ---
 .../frontend/app/components/page-signin/run.ts     |    3 +-
 .../components/modal-import-models/component.js    |    2 +-
 .../app/modules/agent/AgentManager.service.js      |    4 +-
 .../getting-started/GettingStarted.provider.js     |    4 +-
 .../frontend/app/modules/user/Auth.service.ts      |    5 +-
 .../user/{User.service.js => User.service.ts}      |   65 +-
 modules/web-console/frontend/app/vendor.js         |    4 +-
 modules/web-console/frontend/index.js              |    4 +-
 modules/web-console/frontend/package-lock.json     | 1507 +++++++++-----------
 modules/web-console/frontend/package.json          |   25 +-
 .../frontend/public/stylesheets/style.scss         |   18 +-
 modules/web-console/frontend/tsconfig.json         |    7 +-
 .../web-console/frontend/webpack/webpack.common.js |    7 +
 48 files changed, 2211 insertions(+), 1325 deletions(-)

diff --git a/modules/web-console/e2e/testcafe/components/FormField.js b/modules/web-console/e2e/testcafe/components/FormField.js
index 3862390..e0170db 100644
--- a/modules/web-console/e2e/testcafe/components/FormField.js
+++ b/modules/web-console/e2e/testcafe/components/FormField.js
@@ -69,6 +69,19 @@ export class FormField {
     }
 }
 
+export class AngularFormField extends FormField {
+    static ROOT_SELECTOR = 'form-field';
+    static LABEL_SELECTOR = 'label';
+    static CONTROL_SELECTOR = '[formcontrolname]';
+    static ERRORS_SELECTOR = 'form-field-errors';
+
+    async selectOption(label) {
+        await t
+            .click(this.control)
+            .click(this.control.find('option').withText(label));
+    }
+}
+
 /**
  * Not really a custom field, use for form fields at login and profile screens, these don't have "ignite" prefix
  */
diff --git a/modules/web-console/e2e/testcafe/components/PanelCollapsible.js b/modules/web-console/e2e/testcafe/components/PanelCollapsible.js
index c8d49e0..4c8e813 100644
--- a/modules/web-console/e2e/testcafe/components/PanelCollapsible.js
+++ b/modules/web-console/e2e/testcafe/components/PanelCollapsible.js
@@ -25,3 +25,13 @@ export class PanelCollapsible {
         });
     }
 }
+
+export class AngularPanelCollapsible {
+    constructor(title) {
+        this._selector = Selector('panel-collapsible-angular>.heading>.title').withText(title).parent('panel-collapsible-angular');
+        this.heading = this._selector.find('.heading');
+        this.body = this._selector.addCustomDOMProperties({
+            isOpened: (el) => el.hasAttribute('open')
+        });
+    }
+}
diff --git a/modules/web-console/e2e/testcafe/fixtures/user-profile/profile.js b/modules/web-console/e2e/testcafe/fixtures/user-profile/profile.js
index b9a67f9..99a0fe3 100644
--- a/modules/web-console/e2e/testcafe/fixtures/user-profile/profile.js
+++ b/modules/web-console/e2e/testcafe/fixtures/user-profile/profile.js
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-import { Selector } from 'testcafe';
 import { dropTestDB, insertTestUser, resolveUrl } from '../../environment/envtools';
 import { createRegularUser } from '../../roles';
 import {pageProfile} from '../../page-models/pageProfile';
@@ -55,5 +54,5 @@ test('Testing user data change', async(t) => {
         .expect(pageProfile.lastName.control.value).eql(lastName)
         .expect(pageProfile.email.control.value).eql(email)
         .expect(pageProfile.company.control.value).eql(company)
-        .expect(pageProfile.country.control.innerText).eql(country);
+        .expect(pageProfile.country.control.value).eql(country);
 });
diff --git a/modules/web-console/e2e/testcafe/package.json b/modules/web-console/e2e/testcafe/package.json
index 6cba09e..c9dc872 100644
--- a/modules/web-console/e2e/testcafe/package.json
+++ b/modules/web-console/e2e/testcafe/package.json
@@ -24,9 +24,9 @@
   ],
   "dependencies": {
     "app-module-path": "2.2.0",
-    "cross-env": "5.1.1",
-    "glob": "7.1.2",
-    "lodash": "4.17.10",
+    "cross-env": "5.2.0",
+    "glob": "7.1.3",
+    "lodash": "4.17.11",
     "minimist": "1.2.0",
     "mongodb": "2.2.33",
     "node-cmd": "3.0.0",
@@ -38,5 +38,11 @@
     "testcafe-reporter-teamcity": "1.0.9",
     "type-detect": "4.0.3",
     "util": "0.10.3"
+  },
+  "devDependencies": {
+    "@typescript-eslint/eslint-plugin": "^1.7.0",
+    "@typescript-eslint/parser": "^1.7.0",
+    "eslint": "^5.16.0",
+    "typescript": "^3.4.5"
   }
 }
diff --git a/modules/web-console/e2e/testcafe/page-models/pageProfile.js b/modules/web-console/e2e/testcafe/page-models/pageProfile.js
index a130218..08f9195 100644
--- a/modules/web-console/e2e/testcafe/page-models/pageProfile.js
+++ b/modules/web-console/e2e/testcafe/page-models/pageProfile.js
@@ -14,26 +14,26 @@
  * limitations under the License.
  */
 
-import {CustomFormField} from '../components/FormField';
-import {PanelCollapsible} from '../components/PanelCollapsible';
+import {AngularFormField} from '../components/FormField';
+import {AngularPanelCollapsible} from '../components/PanelCollapsible';
 import {Selector} from 'testcafe';
 
 export const pageProfile = {
-    firstName: new CustomFormField({id: 'firstNameInput'}),
-    lastName: new CustomFormField({id: 'lastNameInput'}),
-    email: new CustomFormField({id: 'emailInput'}),
-    phone: new CustomFormField({id: 'phoneInput'}),
-    country: new CustomFormField({id: 'countryInput'}),
-    company: new CustomFormField({id: 'companyInput'}),
+    firstName: new AngularFormField({id: 'firstName'}),
+    lastName: new AngularFormField({id: 'lastName'}),
+    email: new AngularFormField({id: 'email'}),
+    phone: new AngularFormField({id: 'phone'}),
+    country: new AngularFormField({id: 'country'}),
+    company: new AngularFormField({id: 'company'}),
     securityToken: {
-        panel: new PanelCollapsible('security token'),
+        panel: new AngularPanelCollapsible('security token'),
         generateTokenButton: Selector('a').withText('Generate Random Security Token?'),
-        value: new CustomFormField({id: 'securityTokenInput'})
+        value: new AngularFormField({id: 'securityToken'})
     },
     password: {
-        panel: new PanelCollapsible('password'),
-        newPassword: new CustomFormField({id: 'passwordInput'}),
-        confirmPassword: new CustomFormField({id: 'passwordConfirmInput'})
+        panel: new AngularPanelCollapsible('password'),
+        newPassword: new AngularFormField({id: 'newPassword'}),
+        confirmPassword: new AngularFormField({id: 'confirmPassword'})
     },
     saveChangesButton: Selector('.btn-ignite.btn-ignite--success').withText('Save Changes')
 };
diff --git a/modules/web-console/frontend/.babelrc b/modules/web-console/frontend/.babelrc
index b68592c..c9d4d00 100644
--- a/modules/web-console/frontend/.babelrc
+++ b/modules/web-console/frontend/.babelrc
@@ -8,6 +8,7 @@
         "@babel/preset-typescript"
     ],
     "plugins": [
+        ["@babel/plugin-proposal-decorators", {"legacy": true}],
         ["@babel/plugin-proposal-class-properties", { "loose" : true }],
         "@babel/plugin-proposal-object-rest-spread",
         "@babel/plugin-syntax-dynamic-import",
diff --git a/modules/web-console/frontend/.eslintrc b/modules/web-console/frontend/.eslintrc
index 0cfc9a2..bd18a20 100644
--- a/modules/web-console/frontend/.eslintrc
+++ b/modules/web-console/frontend/.eslintrc
@@ -70,7 +70,8 @@ rules:
     max-nested-callbacks: [1, 4]
     max-params: [0, 3]
     max-statements: [0, 10]
-    new-cap: 2
+    // This rule conflicts with TS decorators
+    // new-cap: 2
     new-parens: 2
     no-alert: 2
     no-array-constructor: 2
diff --git a/modules/web-console/frontend/app-angular/components/copyToClipboardButton.component.ts b/modules/web-console/frontend/app-angular/components/copyToClipboardButton.component.ts
new file mode 100644
index 0000000..f5f061d
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/copyToClipboardButton.component.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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, Inject} from '@angular/core';
+import {default as IgniteCopyToClipboardFactory} from 'app/services/CopyToClipboard.service';
+
+@Component({
+    selector: 'copy-to-clipboard-button',
+    template: `
+        <button
+            (click)='copy()'
+            [popper]='content'
+            popperApplyClass='ignite-popper,ignite-popper__tooltip'
+            popperTrigger='hover'
+            popperAppendTo='body'
+            type='button'
+        >
+            <ignite-icon name='copy'></ignite-icon>
+        </button>
+        <popper-content #content><ng-content></ng-content></popper-content>
+    `,
+    styles: [`
+        :host {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+        }
+        button {
+            border: none;
+            margin: 0;
+            padding: 0;
+            background: none;
+            display: inline-flex;
+        }
+        button:hover, button:active {
+            color: #0067b9;
+        }
+    `]
+})
+export class CopyToClipboardButton {
+    @Input()
+    value: string;
+
+    private copy() {
+        this.IgniteCopyToClipboard.copy(this.value);
+    }
+
+    static parameters = [[new Inject('IgniteCopyToClipboard')]];
+
+    constructor(private IgniteCopyToClipboard: ReturnType<typeof IgniteCopyToClipboardFactory>) {}
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/components/form-field/autofocus.directive.ts
similarity index 65%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/components/form-field/autofocus.directive.ts
index a64c892..41b3883 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/components/form-field/autofocus.directive.ts
@@ -14,20 +14,15 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
+import {Directive, AfterViewInit, Inject, ElementRef} from '@angular/core';
 
-    panel-collapsible {
-        width: 100%;
-    }
+@Directive({selector: 'input[autofocus]'})
+export class Autofocus implements AfterViewInit {
+    static parameters = [[new Inject(ElementRef)]];
 
-    footer {
-        display: flex;
-        justify-content: flex-end;
-    }
+    constructor(private el: ElementRef<HTMLInputElement>) {}
 
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
+    ngAfterViewInit() {
+        setTimeout(() => this.el.nativeElement.focus(), 0);
     }
 }
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/components/form-field/error.component.ts
similarity index 50%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/components/form-field/error.component.ts
index a64c892..0ac55c4 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/components/form-field/error.component.ts
@@ -14,20 +14,25 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
+import {Component, Input, ViewChild, Inject, ElementRef, AfterViewInit, TemplateRef} from '@angular/core';
+import {FormField} from './formField.component';
 
-    panel-collapsible {
-        width: 100%;
-    }
+@Component({
+    selector: 'form-field-error',
+    template: `<ng-template #errorTemplate><ng-content></ng-content></ng-template>`
+})
+export class FormFieldError implements AfterViewInit {
+    @Input()
+    error: string;
 
-    footer {
-        display: flex;
-        justify-content: flex-end;
-    }
+    @ViewChild('errorTemplate')
+    template: TemplateRef<any>;
+
+    static parameters = [[new Inject(ElementRef)], [new Inject(FormField)]];
+
+    constructor(private ref: ElementRef, private formField: FormField) {}
 
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
+    ngAfterViewInit() {
+        this.formField.addExtraErrorMessage(this.error, this.template);
     }
 }
diff --git a/modules/web-console/frontend/app/components/page-profile/component.js b/modules/web-console/frontend/app-angular/components/form-field/errorStyles.provider.ts
similarity index 79%
rename from modules/web-console/frontend/app/components/page-profile/component.js
rename to modules/web-console/frontend/app-angular/components/form-field/errorStyles.provider.ts
index 07e5784..ce30704 100644
--- a/modules/web-console/frontend/app/components/page-profile/component.js
+++ b/modules/web-console/frontend/app-angular/components/form-field/errorStyles.provider.ts
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-import template from './template.pug';
-import controller from './controller';
+export enum FormFieldRequiredMarkerStyles {
+    OPTIONAL = 'optional',
+    REQUIRED = 'required'
+}
 
-export default {
-    template,
-    controller
-};
+export enum FormFieldErrorStyles {
+    INLINE = 'inline',
+    ICON = 'icon'
+}
diff --git a/modules/web-console/frontend/app-angular/components/form-field/errors.component.ts b/modules/web-console/frontend/app-angular/components/form-field/errors.component.ts
new file mode 100644
index 0000000..4576b3a
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/form-field/errors.component.ts
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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, Inject} from '@angular/core';
+import {FormFieldErrorStyles} from './errorStyles.provider';
+import {VALIDATION_MESSAGES} from './validationMessages.provider';
+
+@Component({
+    selector: 'form-field-errors',
+    template: `
+        <ng-template #validationMessage>
+            <ng-template *ngIf='extraErrorMessages[errorType]' [ngTemplateOutlet]='extraErrorMessages[errorType]'></ng-template>
+            <ng-container *ngIf='!extraErrorMessages[errorType] && defaultMessages[errorType]'>{{defaultMessages[errorType]}}</ng-container>
+            <ng-container *ngIf='!extraErrorMessages[errorType] && !defaultMessages[errorType]'>Value is invalid: {{errorType}}</ng-container>
+        </ng-template>
+        <div *ngIf='errorStyle === "inline"' class='inline'>
+            <ng-container *ngTemplateOutlet='validationMessage'></ng-container>
+        </div>
+        <div *ngIf='errorStyle === "icon"' class='icon'>
+            <popper-content #popper>
+                <ng-container *ngTemplateOutlet='validationMessage'></ng-container>
+            </popper-content>
+            <ignite-icon
+                name='attention'
+                [popper]='popper'
+                popperApplyClass='ignite-popper,ignite-popper__error'
+                popperTrigger='hover'
+                popperPlacement='top'
+                popperAppendTo='body'
+            ></ignite-icon>
+        </div>
+    `,
+    styles: [`
+        :host {
+            display: block;
+        }
+        .inline {
+            padding: 5px 10px 0;
+            color: #ee2b27;
+            font-size: 12px;
+            line-height: 14px;
+        }
+        .icon {
+            color: #ee2b27;
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    `]
+})
+export class FormFieldErrors<T extends {[errorType: string]: string}> {
+    @Input()
+    errorStyle: FormFieldErrorStyles;
+
+    @Input()
+    extraErrorMessages: T = {} as T;
+
+    @Input()
+    errorType: keyof T;
+
+    static parameters = [[new Inject(VALIDATION_MESSAGES)]];
+
+    constructor(private defaultMessages: VALIDATION_MESSAGES) {}
+}
diff --git a/modules/web-console/frontend/app-angular/components/form-field/formField.component.scss b/modules/web-console/frontend/app-angular/components/form-field/formField.component.scss
new file mode 100644
index 0000000..44ee8f0
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/form-field/formField.component.scss
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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.
+ */
+
+form-field {
+    --required-label-gap: 0.25em;
+    --input-height: 36px;
+    --overlay-item-width: 16px;
+    --overlay-item-gap: 10px;
+
+    &.form-field__icon-error {
+        display: grid;
+        grid-template-areas: 'title title' 'control overlay' 'error-inline error-inline';
+        grid-template-columns: auto min-content;
+
+        .angular-form-field__label {
+            grid-area: title;
+        }
+
+        .angular-form-field__input {
+            grid-column: control / overlay;
+            grid-row: control;
+        }
+    }
+
+    .angular-form-field__input[data-overlay-items-count="1"] {
+        input {
+            padding-right: calc(1 * (var(--overlay-item-width) + var(--overlay-item-gap)) + var(--overlay-item-gap));
+        }
+    }
+
+    .angular-form-field__input[data-overlay-items-count="2"] {
+        input {
+            padding-right: calc(2 * (var(--overlay-item-width) + var(--overlay-item-gap)) + var(--overlay-item-gap));
+        }
+    }
+
+    .input-overlay {
+        grid-area: overlay;
+        display: grid;
+        justify-content: flex-end;
+        grid-auto-columns: var(--overlay-item-width);
+        grid-auto-flow: column;
+        padding-right: var(--overlay-item-gap);
+        grid-gap: var(--overlay-item-gap);
+        z-index: 2;
+        // Fixes z-order in Edge
+        transform: translateZ(1px);
+    }
+
+    input, select {
+        box-sizing: border-box;
+        height: var(input-height);
+        padding: 9px 10px;
+        border: solid 1px #c5c5c5;
+        border-radius: 4px;
+        background-color: #ffffff;
+        color: #393939;
+        font-size: 14px;
+        line-height: 16px;
+        width: 100%;
+
+        &[disabled] {
+            opacity: 0.5;
+        }
+
+        &::placeholder {
+            color: rgba(66, 66, 66, 0.5);
+            text-align: left;
+        }
+    }
+    select {
+        -webkit-appearance: unset;
+        appearance: unset;
+        background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA4IDQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTAgMGg4bC00IDR6IiBmaWxsPSIjMzkzOTM5Ii8+PC9zdmc+);
+        background-size: 8px 4px;
+        background-repeat: no-repeat;
+        background-position: calc(100% - 10px) 50%;
+    }
+    .angular-form-field__label {
+        display: flex;
+    }
+    .angular-form-field__label {
+        margin: 0 0 4px;
+        color: #424242;
+        font-size: 14px;
+        line-height: 1.25;
+        width: 100%;
+
+        &:before {
+            content: ':';
+            order: 2;
+        }
+
+        form-field-tooltip {
+            order: 3;
+            margin-left: var(--required-label-gap);
+            align-self: center;
+        }
+    }
+    &.form-field__optional .angular-form-field__label {
+        &:after {
+            content: '(optional)';
+            color: #757575;
+            order: 1;
+            margin-left: var(--required-label-gap);
+        }
+    }
+    &.form-field__required .angular-form-field__label {
+        &:after {
+            content: '*';
+            color: #ee2b27;
+            order: 4;
+            margin-left: var(--required-label-gap);
+        }
+    }
+
+    &[type='checkbox'] {
+        grid-template-areas: 'control title overlay' 'error-inline error-inline error-inline';
+        grid-template-columns: 12px 1fr auto;
+
+        .angular-form-field__label {
+            margin: 0 0 0 10px;
+
+            &:before {
+                display: none !important;
+            }
+        }
+
+        .angular-form-field__input {
+            grid-area: control;
+
+            &> input[type='checkbox'] {
+                border-radius: 2px;
+
+                background-image: url(/images/checkbox.svg);
+                width: 12px !important;
+                height: 12px !important;
+                -webkit-appearance: none;
+                -moz-appearance: none;
+                appearance: none;
+                background-repeat: no-repeat;
+                background-size: 100%;
+                padding: 0;
+                margin: 0;
+                border: none;
+
+                &:checked {
+                    background-image: url(/images/checkbox-active.svg);
+                }
+
+                &:disabled {
+                    opacity: 0.5;
+                }
+
+                &:focus {
+                    outline: none;
+                    box-shadow: 0 0 0 2px rgba(0, 103, 185, .3);
+                }
+            }
+        }
+    }
+}
diff --git a/modules/web-console/frontend/app-angular/components/form-field/formField.component.ts b/modules/web-console/frontend/app-angular/components/form-field/formField.component.ts
new file mode 100644
index 0000000..7e84220
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/form-field/formField.component.ts
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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, AfterViewInit, Inject, Input, ContentChild, HostBinding, Injectable} from '@angular/core';
+import {FormFieldErrorStyles, FormFieldRequiredMarkerStyles} from './index';
+import {FormFieldHint} from './hint.component';
+import {FormControlName, FormControl} from '@angular/forms';
+import './formField.component.scss';
+
+@Injectable({
+    providedIn: 'root'
+})
+export class FORM_FIELD_OPTIONS {
+    requiredMarkerStyle: FormFieldRequiredMarkerStyles = FormFieldRequiredMarkerStyles.REQUIRED;
+    errorStyle: FormFieldErrorStyles = FormFieldErrorStyles.ICON
+}
+
+@Component({
+    selector: 'form-field',
+    template: `
+        <ng-template #errors>
+            <form-field-errors
+                *ngIf='(control?.dirty || control?.touched) && control?.invalid'
+                [errorStyle]='errorStyle'
+                [errorType]='_getErrorType(control?.control)'
+                [extraErrorMessages]='extraMessages'
+            ></form-field-errors>
+        </ng-template>
+        <div class="angular-form-field__label">
+            <ng-content select="label"></ng-content>
+            <form-field-tooltip *ngIf='hint' [content]='hint.popper'></form-field-tooltip>
+        </div>
+        <div class="angular-form-field__input" [attr.data-overlay-items-count]='overlayEl.childElementCount'>
+            <ng-content></ng-content>
+        </div>
+        <div class="input-overlay" #overlayEl>
+            <ng-container *ngIf='errorStyle === "icon"'>
+                <ng-container *ngTemplateOutlet='errors'></ng-container>
+            </ng-container>
+            <ng-content select='[formFieldOverlay]'></ng-content>
+        </div>
+        <ng-container *ngIf='errorStyle === "inline"'>
+            <ng-container *ngTemplateOutlet='errors'></ng-container>
+        </ng-container>
+    `,
+    styles: [`
+        .angular-form-field__input {
+            position: relative;
+        }
+        .input-overlay {
+            display: grid;
+        }
+    `]
+})
+export class FormField implements AfterViewInit {
+    static parameters = [[new Inject(FORM_FIELD_OPTIONS)]];
+
+    constructor(options: FORM_FIELD_OPTIONS) {
+        this.errorStyle = options.errorStyle;
+        this.requiredMarkerStyle = options.requiredMarkerStyle;
+    }
+
+    @Input()
+    errorStyle: FormFieldErrorStyles;
+
+    @Input()
+    requiredMarkerStyle: FormFieldRequiredMarkerStyles;
+
+    extraMessages = {};
+
+    @ContentChild(FormControlName)
+    control: FormControlName;
+
+    @ContentChild(FormFieldHint)
+    hint: FormFieldHint;
+
+    @HostBinding('class.form-field__required')
+    isRequired: boolean;
+
+    @HostBinding('class.form-field__optional')
+    isOptional: boolean;
+
+    @HostBinding('class.form-field__icon-error')
+    get isIconError() {
+        return this.errorStyle === FormFieldErrorStyles.ICON;
+    }
+
+    @HostBinding('class.form-field__inline-error')
+    get isInlineError() {
+        return this.errorStyle === FormFieldErrorStyles.INLINE;
+    }
+
+    ngAfterViewInit() {
+        // setTimeout fixes ExpressionChangedAfterItHasBeenCheckedError
+        setTimeout(() => {
+            const hasRequired: boolean = this.control && this.control.control && this.control.control.validator && this.control.control.validator({}).required;
+            this.isOptional = this.requiredMarkerStyle === FormFieldRequiredMarkerStyles.OPTIONAL && !hasRequired;
+            this.isRequired = this.requiredMarkerStyle === FormFieldRequiredMarkerStyles.REQUIRED && hasRequired;
+        }, 0);
+    }
+
+    _getErrorType(control: FormControl): string {
+        return control.errors ? Object.entries(control.errors).filter(([key, invalid]) => invalid).map(([key]) => key).pop() : void 0;
+    }
+
+    addExtraErrorMessage(key, message) {
+        this.extraMessages = {
+            ...this.extraMessages,
+            [key]: message
+        };
+    }
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/components/form-field/hint.component.ts
similarity index 67%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/components/form-field/hint.component.ts
index a64c892..84eb747 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/components/form-field/hint.component.ts
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
+import {Component, ViewChild} from '@angular/core';
+import {PopperContent} from 'ngx-popper';
 
-    panel-collapsible {
-        width: 100%;
-    }
-
-    footer {
-        display: flex;
-        justify-content: flex-end;
-    }
-
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
-    }
+@Component({
+    selector: 'form-field-hint',
+    template: `
+        <popper-content>
+            <ng-content></ng-content>
+        </popper-content>
+    `
+})
+export class FormFieldHint {
+    @ViewChild(PopperContent)
+    popper: PopperContent
 }
diff --git a/modules/web-console/frontend/app-angular/components/form-field/index.ts b/modules/web-console/frontend/app-angular/components/form-field/index.ts
new file mode 100644
index 0000000..50fe9bc
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/form-field/index.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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 {FormFieldHint} from './hint.component';
+import {FormFieldError} from './error.component';
+import {FormField, FORM_FIELD_OPTIONS} from './formField.component';
+import {Autofocus} from './autofocus.directive';
+import {FormFieldTooltip} from './tooltip.component';
+import {PasswordVisibilityToggleButton} from './passwordVisibilityToggleButton.component';
+import {ScrollToFirstInvalid} from './scrollToFirstInvalid.directive';
+
+export {
+    FormFieldHint,
+    FormFieldError,
+    FormField, FORM_FIELD_OPTIONS,
+    Autofocus,
+    FormFieldTooltip,
+    PasswordVisibilityToggleButton,
+    ScrollToFirstInvalid
+};
+
+export * from './errorStyles.provider';
+export * from './validationMessages.provider';
diff --git a/modules/web-console/frontend/app-angular/components/form-field/passwordVisibilityToggleButton.component.ts b/modules/web-console/frontend/app-angular/components/form-field/passwordVisibilityToggleButton.component.ts
new file mode 100644
index 0000000..67b6855
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/form-field/passwordVisibilityToggleButton.component.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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} from '@angular/core';
+
+@Component({
+    selector: 'password-visibility-toggle-button-angular',
+    template: `
+        <button
+            type='button'
+            (click)='toggleVisibility()'
+            [popper]='isVisible ? "Hide password" : "Show password"'
+            popperApplyClass='ignite-popper,ignite-popper__tooltip'
+            popperAppendTo='body'
+            popperTrigger='hover'
+            popperPlacement='top'
+        >
+            <ignite-icon [name]='isVisible ? "eyeOpened" : "eyeClosed"'></ignite-icon>
+        </button>
+    `,
+    styles: [`
+        :host {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+        }
+        button {
+            border: none;
+            margin: 0;
+            padding: 0;
+            background: none;
+            display: inline-flex;
+        }
+        button:hover, button:active {
+            color: #0067b9;
+        }
+    `]
+})
+export class PasswordVisibilityToggleButton {
+    @Input()
+    passwordEl: HTMLInputElement;
+
+    isVisible: boolean = false;
+
+    toggleVisibility() {
+        this.isVisible = !this.isVisible;
+        this.passwordEl.setAttribute('type', this.isVisible ? 'text' : 'password');
+    }
+}
diff --git a/modules/web-console/frontend/app-angular/components/form-field/scrollToFirstInvalid.directive.ts b/modules/web-console/frontend/app-angular/components/form-field/scrollToFirstInvalid.directive.ts
new file mode 100644
index 0000000..9762cb2
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/form-field/scrollToFirstInvalid.directive.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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 {Directive, HostListener, Input, Inject, ElementRef} from '@angular/core';
+import {NgForm} from '@angular/forms';
+
+@Directive({
+    selector: '[scrollToFirstInvalid]'
+})
+export class ScrollToFirstInvalid {
+    @Input()
+    formGroup: NgForm;
+
+    @HostListener('ngSubmit', ['$event'])
+    onSubmit(e: Event) {
+        if (this.formGroup.invalid) {
+            const invalidEl = this.findFirstInvalid();
+            if (invalidEl) {
+                this.scrollIntoView(invalidEl);
+                invalidEl.focus();
+                invalidEl.blur();
+                invalidEl.focus();
+                setTimeout(() => this.maybeShowValidationErrorPopover(invalidEl), 0);
+            }
+        }
+    }
+
+    private maybeShowValidationErrorPopover(el: HTMLInputElement): void {
+        try {
+            el.closest('form-field').querySelector('form-field-errors ignite-icon').dispatchEvent(new MouseEvent('mouseenter'));
+        }
+        catch (ignored) {
+            // No-op.
+        }
+    }
+
+    private findFirstInvalid(): HTMLInputElement | null {
+        return this.el.nativeElement.querySelector('.ng-invalid:not(panel-collapsible-angular)');
+    }
+
+    private scrollIntoView(el: Element): void {
+        el.scrollIntoView({block: 'center'});
+    }
+
+    static parameters = [[new Inject(ElementRef)]];
+
+    constructor(private el: ElementRef<HTMLFormElement>) {}
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/index.js b/modules/web-console/frontend/app-angular/components/form-field/tooltip.component.ts
similarity index 52%
rename from modules/web-console/frontend/app/components/page-profile/index.js
rename to modules/web-console/frontend/app-angular/components/form-field/tooltip.component.ts
index 2072cfd..23f50ac 100644
--- a/modules/web-console/frontend/app/components/page-profile/index.js
+++ b/modules/web-console/frontend/app-angular/components/form-field/tooltip.component.ts
@@ -14,23 +14,30 @@
  * limitations under the License.
  */
 
-import angular from 'angular';
-import component from './component';
-import './style.scss';
+import {Component, Input} from '@angular/core';
+import {PopperContent} from 'ngx-popper';
 
-export default angular
-    .module('ignite-console.page-profile', [
-        'ignite-console.user'
-    ])
-    .config(['$stateProvider', ($stateProvider) => {
-        // set up the states
-        $stateProvider.state('base.settings.profile', {
-            url: '/profile',
-            component: 'pageProfile',
-            permission: 'profile',
-            tfMetaTags: {
-                title: 'User profile'
-            }
-        });
-    }])
-    .component('pageProfile', component);
+@Component({
+    selector: 'form-field-tooltip',
+    template: `
+        <ignite-icon
+            name='info'
+            [popper]='content'
+            popperApplyClass='ignite-popper,ignite-popper__tooltip'
+            popperTrigger='hover'
+            popperAppendTo='body'
+        ></ignite-icon>
+    `,
+    styles: [`
+        :host {
+            display: inline-flex;
+        }
+        ignite-icon {
+            color: #0067b9;
+        }
+    `]
+})
+export class FormFieldTooltip {
+    @Input()
+    content: PopperContent
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/components/form-field/validationMessages.provider.ts
similarity index 73%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/components/form-field/validationMessages.provider.ts
index a64c892..d2a192a 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/components/form-field/validationMessages.provider.ts
@@ -14,20 +14,13 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
+import {Injectable} from '@angular/core';
 
-    panel-collapsible {
-        width: 100%;
-    }
-
-    footer {
-        display: flex;
-        justify-content: flex-end;
-    }
-
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
-    }
+@Injectable({
+    providedIn: 'root'
+})
+export class VALIDATION_MESSAGES {
+    required = 'Value is required';
+    email = 'Email has invalid format';
+    passwordMatch = 'Passwords do not match';
 }
diff --git a/modules/web-console/frontend/app-angular/components/igniteIcon.component.ts b/modules/web-console/frontend/app-angular/components/igniteIcon.component.ts
new file mode 100644
index 0000000..526bdb6
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/igniteIcon.component.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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, Inject, Input, OnChanges} from '@angular/core';
+import {default as IgniteIconsService} from 'app/components/ignite-icon/service';
+
+@Component({
+    selector: 'ignite-icon',
+    template: `<svg [attr.viewBox]='viewbox'><svg:use [attr.xlink:href]="url" [attr.href]='url'></svg:use></svg>`,
+    styles: [`
+        :host {
+            width: 16px;
+            height: 16px;
+            display: inline-flex;
+        }
+        svg {
+            width: 100%;
+            height: 100%;
+        }
+    `]
+})
+export class IgniteIcon implements OnChanges {
+    @Input()
+    name: string;
+    viewbox: string;
+
+    static parameters = [
+        [new Inject('IgniteIcon')]
+    ];
+
+    constructor(private icons: IgniteIconsService) {}
+
+    get url() {
+        return `${window.location.href}#${this.name}`;
+    }
+
+    ngOnChanges() {
+        const icon = this.icons.getIcon(this.name);
+        if (icon)
+            this.viewbox = icon.viewBox;
+    }
+}
diff --git a/modules/web-console/frontend/app-angular/components/page-profile/component.ts b/modules/web-console/frontend/app-angular/components/page-profile/component.ts
new file mode 100644
index 0000000..a200b52
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/page-profile/component.ts
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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 omit from 'lodash/fp/omit';
+import {merge} from 'rxjs';
+import {tap, filter} from 'rxjs/operators';
+import {Component, Inject, OnInit, OnDestroy} from '@angular/core';
+import {FormGroup, FormControl, Validators, FormBuilder} from '@angular/forms';
+import templateUrl from 'file-loader!./template.html';
+import {default as CountriesFactory, Country} from 'app/services/Countries.service';
+import {default as UserFactory, User} from 'app/modules/user/User.service';
+import {Confirm} from 'app/services/Confirm.service';
+import {default as LegacyUtilsFactory} from 'app/services/LegacyUtils.service';
+import {
+    FORM_FIELD_OPTIONS, FormFieldRequiredMarkerStyles, FormFieldErrorStyles
+} from '../form-field';
+import './style.scss';
+
+const passwordMatch = (newPassword: string) => (confirmPassword: FormControl) => newPassword === confirmPassword.value
+    ? null
+    : {passwordMatch: true};
+
+const disableFormGroup = (fg: FormGroup) => {fg.disable(); return fg;};
+
+@Component({
+    selector: 'page-profile',
+    templateUrl,
+    viewProviders: [
+        {
+            provide: FORM_FIELD_OPTIONS,
+            useValue: {
+                requiredMarkerStyle: FormFieldRequiredMarkerStyles.OPTIONAL,
+                errorStyle: FormFieldErrorStyles.ICON
+            }
+        }
+    ]
+})
+export class PageProfile implements OnInit, OnDestroy {
+    static parameters = [
+        [new Inject('IgniteCountries')],
+        [new Inject('User')],
+        [new Inject('Confirm')],
+        [new Inject('IgniteLegacyUtils')],
+        [new Inject(FormBuilder)]
+    ];
+
+    constructor(
+        Countries: ReturnType<typeof CountriesFactory>,
+        private User: ReturnType<typeof UserFactory>,
+        private Confirm: Confirm,
+        private LegacyUtils: ReturnType<typeof LegacyUtilsFactory>,
+        private fb: FormBuilder
+    ) {
+        this.countries = Countries.getAll();
+    }
+
+    countries: Country[];
+    user: User;
+    isLoading: boolean = false;
+
+    async ngOnInit() {
+        this.user = await this.User.read();
+        this.form.patchValue(this.user);
+    }
+
+    ngOnDestroy() {
+        this.subscriber.unsubscribe();
+    }
+
+    async saveUser(): Promise<void> {
+        if (this.form.invalid) return;
+        this.isLoading = true;
+        try {
+            const user = await this.User.save(this.prepareFormValue(this.form));
+            this.form.patchValue(user);
+            this.form.get('passwordPanelOpened').setValue(false);
+        } finally {
+            this.isLoading = false;
+        }
+    }
+
+    prepareFormValue(form: PageProfile['form']): Partial<User> {
+        return {
+            ...omit(['password', 'passwordPanelOpened'])(form.value),
+            token: form.controls.token.value,
+            ...form.value.passwordPanelOpened ? {password: form.value.password.new} : {}
+        };
+    }
+
+    async generateToken() {
+        try {
+            await this.Confirm.confirm('Are you sure you want to change security token?<br>If you change the token you will need to restart the agent.');
+            this.form.get('token').setValue(this.LegacyUtils.randomString(20));
+        }
+        catch (ignored) {
+            // No-op.
+        }
+    }
+
+    form = this.fb.group({
+        firstName: ['', Validators.required],
+        lastName: ['', Validators.required],
+        email: ['', [Validators.required, Validators.email]],
+        phone: '',
+        country: ['', Validators.required],
+        company: ['', Validators.required],
+        password: disableFormGroup(this.fb.group({
+            new: ['', Validators.required],
+            confirm: ''
+        })),
+        tokenPanelOpened: false,
+        passwordPanelOpened: false,
+        token: this.fb.control({value: '', disabled: true}, [Validators.required])
+    });
+
+    subscriber = merge(
+        this.form.get('passwordPanelOpened').valueChanges.pipe(
+            tap((opened: boolean) => {
+                this.form.get('password')[opened ? 'enable' : 'disable']();
+                this.form.get('password').updateValueAndValidity();
+                if (opened) this.form.get('password').reset();
+            })
+        ),
+        this.form.get('password.new').valueChanges.pipe(
+            tap((newPassword: string) => {
+                this.form.get('password.confirm').setValidators([Validators.required, passwordMatch(newPassword)]);
+                this.form.get('password.confirm').updateValueAndValidity();
+            })
+        ),
+        this.form.get('tokenPanelOpened').valueChanges.pipe(
+            filter((opened) => opened === false),
+            tap(async() => {
+                this.form.get('token').reset((await this.User.read()).token);
+            })
+        )
+    ).subscribe();
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/components/page-profile/style.scss
similarity index 63%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/components/page-profile/style.scss
index a64c892..c0170f9 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/components/page-profile/style.scss
@@ -15,19 +15,33 @@
  */
 
 page-profile {
-    max-width: 800px;
     display: block;
+    max-width: 800px;
 
-    panel-collapsible {
-        width: 100%;
+    .form-grid {
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        grid-gap: var(--form-gap);
+
+        .span-1 {
+            grid-column: span 1;
+        }
+        .span-2 {
+            grid-column: span 2;
+        }
     }
 
-    footer {
+    .security-token-generate-button-container {
+        align-items: flex-end;
         display: flex;
-        justify-content: flex-end;
+        padding-bottom: 9px;
     }
 
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
+    footer {
+        display: grid;
+        grid-auto-flow: column;
+        grid-auto-columns: max-content;
+        justify-content: flex-end;
+        grid-gap: var(--form-gap);
     }
 }
diff --git a/modules/web-console/frontend/app-angular/components/page-profile/template.html b/modules/web-console/frontend/app-angular/components/page-profile/template.html
new file mode 100644
index 0000000..df8952b
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/page-profile/template.html
@@ -0,0 +1,93 @@
+<!--
+ Copyright 2019 GridGain Systems, Inc. and Contributors.
+ 
+ Licensed under the GridGain Community Edition License (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ 
+     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ 
+ 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.
+-->
+
+<global-progress-line [isLoading]='isLoading'></global-progress-line>
+<header class="header-with-selector">
+    <div><h1>User profile</h1></div>        
+</header>
+<form [formGroup]='form' (ngSubmit)='saveUser()' id='user' scrollToFirstInvalid class="form-grid">
+    <form-field class='span-1'>
+        <label for='firstName'>First name</label>
+        <input type="text" formControlName='firstName' id='firstName' autofocus>
+    </form-field>
+    <form-field class='span-1'>
+        <label for='lastName'>Last name</label>
+        <input type="text" formControlName='lastName' id='lastName'>
+    </form-field>
+    <form-field class='span-2'>
+        <label for='email'>Email</label>
+        <input type="email" formControlName='email' id='email' autocomplete="username email">
+    </form-field>
+    <form-field class='span-1'>
+        <label for='phone'>Phone</label>
+        <input type="tel" formControlName='phone' id='phone' placeholder='Input phone (ex.: +15417543010)'>
+    </form-field>
+    <form-field class='span-1'>
+        <label for='country'>Country/Region</label>
+        <select formControlName='country' id='country'>
+            <option *ngFor='let country of countries' [value]="country.value">{{country.label}}</option>
+        </select>
+    </form-field>
+    <form-field class='span-2'>
+        <label for='company'>Company</label>
+        <input type="text" formControlName='company' id='company'>
+    </form-field>
+    <panel-collapsible-angular class='span-2' #securityTokenPanel formControlName='tokenPanelOpened'>
+        <span panelTitle>{{ securityTokenPanel.opened ? 'Cancel security token changing...' : 'Show security token...' }}</span>
+        <div class='form-grid'>
+            <form-field class="span-1">
+                <form-field-hint>The security token is used for authentication of Web agent</form-field-hint>
+                <label for="securityToken">Security Token</label>
+                <copy-to-clipboard-button formFieldOverlay [value]='form.controls.token.value'>Copy security token to clipboard</copy-to-clipboard-button>
+                <input
+                    id='securityToken'
+                    type="text"
+                    placeholder='No security token. Please regenerate.'
+                    formControlName='token'
+                >
+            </form-field>
+            <span class="span-1 security-token-generate-button-container">
+                <a (click)='generateToken()'>Generate Random Security Token?</a>
+            </span>
+        </div>
+    </panel-collapsible-angular>
+    <panel-collapsible-angular class='span-2' formControlName='passwordPanelOpened' [formGroup]='form.controls.password'>
+        <span panelTitle>{{ form.value.passwordPanelOpened ? 'Cancel password changing...' : 'Change password...' }}</span>
+        <div class="form-grid" *ngIf='form.value.passwordPanelOpened'>
+            <!-- https://www.chromium.org/developers/design-documents/create-amazing-password-forms -->
+            <!-- https://stackoverflow.com/a/48736294/333777 -->
+            <input type="text" name="email" [value]='form.value.email' autocomplete="username email" style="display: none;">
+            <form-field class='span-2'>
+                <label for="newPassword">New password</label>
+                <password-visibility-toggle-button-angular formFieldOverlay [passwordEl]='passwordEl'></password-visibility-toggle-button-angular>
+                <input type="password" formControlName='new' id='newPassword' #passwordEl autofocus autocomplete="new-password">
+            </form-field>
+            <form-field class='span-2'>
+                <label for="confirmPassword">Confirm password</label>
+                <password-visibility-toggle-button-angular formFieldOverlay [passwordEl]='confirmPasswordEl'></password-visibility-toggle-button-angular>
+                <input type="password" formControlName='confirm' id='confirmPassword' #confirmPasswordEl autocomplete="new-password">
+            </form-field>
+        </div>
+    </panel-collapsible-angular>
+</form>
+<hr>
+<footer>
+    <a class="btn-ignite btn-ignite--link-success" uiSref="default-state">Cancel</a>
+    <button type='submit' class="btn-ignite btn-ignite--success" form='user' [disabled]='isLoading'>
+        <ignite-icon name="checkmark" class='icon-left'></ignite-icon>
+        Save Changes
+    </button>
+</footer>
\ No newline at end of file
diff --git a/modules/web-console/frontend/app-angular/components/panelCollapsible.component.ts b/modules/web-console/frontend/app-angular/components/panelCollapsible.component.ts
new file mode 100644
index 0000000..3c55187
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/components/panelCollapsible.component.ts
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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, forwardRef, HostBinding} from '@angular/core';
+import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
+
+@Component({
+    selector: 'panel-collapsible-angular',
+    template: `
+        <div class="heading" (click)='toggle()' (keyup.enter)='toggle()' (keyup.space)='toggle()' tabindex='0'>
+            <ignite-icon class='status' [name]='opened ? "collapse" : "expand"'></ignite-icon>
+            <div class="title">
+                <ng-content select='[panelTitle]'></ng-content>
+            </div>
+            <div class="description">
+                <ng-content select='[panelDescription]'></ng-content>
+            </div>
+            <div class="actions" (click)='$event.stopPropagation()'>
+                <ng-content select='[panelActions]'></ng-content>
+            </div>
+        </div>
+        <div class="content" [hidden]='!opened'>
+            <ng-content></ng-content>
+        </div>
+    `,
+    styles: [`
+        :host {
+            display: flex;
+            flex-direction: column;
+            border-radius: 0 0 4px 4px;
+            background-color: #ffffff;
+            box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
+        }
+
+        :host[disabled] {
+            opacity: 0.5;
+        }
+
+        :host > .heading .status {
+            margin-right: 10px;
+            color: #757575;
+            width: 13px;
+            height: 13px;
+        }
+
+        :host > .heading {
+            padding: 30px 20px 30px;
+            color: #393939;
+            display: flex;
+            flex-direction: row;
+            align-items: baseline;
+            user-select: none;
+            cursor: pointer;
+            line-height: 1.42857;
+        }
+
+        :host > .heading .title {
+            font-size: 16px;
+            margin-right: 8px;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
+
+        :host > .heading .description {
+            font-size: 12px;
+            color: #757575;
+            flex: 1 1;
+        }
+
+        :host > .heading .actions {
+            margin-left: auto;
+            flex: 0 0 auto;
+
+            & > panel-actions {
+                display: flex;
+                flex-direction: row;
+
+                & > * {
+                    flex: 0 0 auto;
+                    margin-left: 10px;
+                }
+            }
+        }
+
+        :host > .content {
+            border-top: 1px solid #dddddd;
+            padding: 15px 20px;
+        }
+    `],
+    providers: [{
+        provide: NG_VALUE_ACCESSOR,
+        useExisting: forwardRef(() => PanelCollapsible),
+        multi: true
+    }]
+})
+export class PanelCollapsible implements ControlValueAccessor {
+    @Input()
+    opened: boolean = false;
+
+    @HostBinding('attr.open')
+    private get openAttr() {
+        return this.opened ? '' : void 0;
+    }
+
+    @Input()
+    disabled: string;
+
+    private _onChange?: (value: PanelCollapsible['opened']) => void;
+
+    toggle() {
+        if (this.opened)
+            this.close();
+        else
+            this.open();
+    }
+
+    open() {
+        if (this.disabled) return;
+        this.opened = true;
+        if (this._onChange) this._onChange(this.opened);
+    }
+
+    close() {
+        if (this.disabled) return;
+        this.opened = false;
+        if (this._onChange) this._onChange(this.opened);
+    }
+
+    writeValue(value: PanelCollapsible['opened']) {
+        this.opened = value;
+    }
+
+    registerOnChange(fn) {
+        this._onChange = fn;
+    }
+
+    registerOnTouched() {}
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/components/serviceBootstrap.ts
similarity index 59%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/components/serviceBootstrap.ts
index a64c892..486a12f 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/components/serviceBootstrap.ts
@@ -14,20 +14,22 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
-
-    panel-collapsible {
-        width: 100%;
-    }
+/**
+ * This component ensures that Angular starts before AngularJS
+ * https://github.com/ui-router/angular-hybrid/issues/98
+ */
 
-    footer {
-        display: flex;
-        justify-content: flex-end;
-    }
+import {Component} from '@angular/core';
 
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
+@Component({
+    selector: 'service-bootstrap',
+    template: ''
+})
+export class ServiceBootstrapComponent {
+    constructor() {
+        const injector = angular.element(document.body).injector();
+        const urlService = injector.get('$urlService');
+        urlService.listen();
+        urlService.sync();
     }
 }
diff --git a/modules/web-console/frontend/app-angular/downgrade.ts b/modules/web-console/frontend/app-angular/downgrade.ts
new file mode 100644
index 0000000..4c86835
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/downgrade.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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 {ServiceBootstrapComponent} from './components/serviceBootstrap';
+import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
+import {downgradeModule, downgradeComponent } from '@angular/upgrade/static';
+import {StaticProvider} from '@angular/core';
+
+export const downgradeModuleFactory = (ngModule) => {
+    const boostrapFn = (extraProviders: StaticProvider[]) => {
+        const platformRef = platformBrowserDynamic(extraProviders);
+        return platformRef.bootstrapModule(ngModule);
+    };
+    const downgradedModule = downgradeModule(boostrapFn);
+
+    angular
+        .module(downgradedModule)
+        .directive('serviceBootstrap', downgradeComponent({component: ServiceBootstrapComponent}))
+        .run(['$compile', '$rootScope', ($compile: ng.ICompileService, $root: ng.IRootScopeService) => {
+            $compile('<service-bootstrap></service-bootstrap>')($root);
+        }]);
+
+    return downgradedModule;
+};
diff --git a/modules/web-console/frontend/app-angular/index.ts b/modules/web-console/frontend/app-angular/index.ts
new file mode 100644
index 0000000..9cd6161
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/index.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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.
+ */
+
+// See https://angular.io/guide/deployment#enable-runtime-production-mode for details.
+import {enableProdMode} from '@angular/core';
+if (process.env.NODE_ENV === 'production') enableProdMode();
+
+import './style.scss';
+
+import {NgModule, Inject} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {UIRouterUpgradeModule} from '@uirouter/angular-hybrid';
+import {UIRouter} from '@uirouter/angular';
+import {UpgradeModule} from '@angular/upgrade/static';
+import {NgxPopperModule} from 'ngx-popper';
+
+import {ServiceBootstrapComponent} from './components/serviceBootstrap';
+export {ServiceBootstrapComponent};
+import {PageProfile} from './components/page-profile/component';
+export {PageProfile};
+import {IgniteIcon} from './components/igniteIcon.component';
+import {PanelCollapsible} from './components/panelCollapsible.component';
+import {CopyToClipboardButton} from './components/copyToClipboardButton.component';
+
+import {FormFieldHint} from './components/form-field/hint.component';
+import {FormFieldErrors} from './components/form-field/errors.component';
+import {FormFieldError} from './components/form-field/error.component';
+import {FormField} from './components/form-field/formField.component';
+import {Autofocus} from './components/form-field/autofocus.directive';
+import {FormFieldTooltip} from './components/form-field/tooltip.component';
+import {PasswordVisibilityToggleButton} from './components/form-field/passwordVisibilityToggleButton.component';
+import {ScrollToFirstInvalid} from './components/form-field/scrollToFirstInvalid.directive';
+
+import {ReactiveFormsModule} from '@angular/forms';
+
+import {upgradedComponents} from './upgrade';
+
+export const declarations = [
+    ServiceBootstrapComponent,
+    PageProfile,
+    IgniteIcon,
+    PanelCollapsible,
+    CopyToClipboardButton,
+    FormFieldHint,
+    FormFieldErrors,
+    FormFieldError,
+    FormField,
+    Autofocus,
+    FormFieldTooltip,
+    PasswordVisibilityToggleButton,
+    ScrollToFirstInvalid,
+    ...upgradedComponents
+];
+
+export const entryComponents = [
+    ServiceBootstrapComponent,
+    PageProfile
+];
+
+export const upgradeService = (token: string) => ({
+    provide: token,
+    useFactory: (i) => i.get(token),
+    deps: ['$injector']
+});
+
+export const providers = [
+    'IgniteLegacyUtils',
+    'Confirm',
+    'IgniteCountries',
+    'User',
+    'IgniteIcons',
+    'IgniteCopyToClipboard'
+].map(upgradeService);
+
+import {states} from './states';
+
+@NgModule({
+    imports: [
+        BrowserModule,
+        ReactiveFormsModule,
+        UpgradeModule,
+        UIRouterUpgradeModule.forRoot({states}),
+        NgxPopperModule.forRoot({
+            applyClass: 'ignite-popper',
+            appendTo: 'body',
+            boundariesElement: 'ui-view.content'
+        })
+    ],
+    providers,
+    declarations,
+    entryComponents,
+    exports: [
+        ...declarations,
+        NgxPopperModule
+    ]
+})
+export class IgniteWebConsoleModule {
+    static parameters = [[new Inject(UIRouter)]];
+
+    constructor(private router: UIRouter) {}
+
+    ngDoBootstrap() {}
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/states.ts
similarity index 66%
copy from modules/web-console/frontend/app/components/page-profile/style.scss
copy to modules/web-console/frontend/app-angular/states.ts
index a64c892..0235fc24 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/states.ts
@@ -14,20 +14,17 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
+import {NgHybridStateDeclaration} from '@uirouter/angular-hybrid';
+import {PageProfile} from '.';
 
-    panel-collapsible {
-        width: 100%;
+export const states: NgHybridStateDeclaration[] = [
+    {
+        name: 'base.settings.profile',
+        url: '/profile',
+        component: PageProfile,
+        permission: 'profile',
+        tfMetaTags: {
+            title: 'User profile'
+        }
     }
-
-    footer {
-        display: flex;
-        justify-content: flex-end;
-    }
-
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
-    }
-}
+];
diff --git a/modules/web-console/frontend/app-angular/style.scss b/modules/web-console/frontend/app-angular/style.scss
new file mode 100644
index 0000000..774f8f4
--- /dev/null
+++ b/modules/web-console/frontend/app-angular/style.scss
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ * 
+ * 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.
+ */
+
+.ignite-popper.ngxp__container {
+    --arrow-offset: 6px;
+    --target-margin: 8px;
+
+    background-color: var(--ignite-popper-background-color);
+    box-shadow: none !important;
+    border-color: var(--ignite-popper-border-color);
+    color: var(--ignite-popper-font-color);
+    padding: 12px;
+    border-radius: 4px;
+    max-width: 400px;
+    // Above app header
+    z-index: 11;
+
+    &[x-placement^="right"] {
+        margin-left: var(--target-margin);
+
+        .ngxp__arrow {
+            left: calc(-1 * var(--arrow-offset))
+        }
+    }
+
+    &[x-placement^="left"] {
+        margin-right: var(--target-margin);
+
+        .ngxp__arrow {
+            right: calc(-1 * var(--arrow-offset))
+        }
+    }
+
+    .ngxp__inner {
+        font-size: 12px;
+    }
+
+    .ngxp__arrow {
+        border-color: var(--ignite-popper-border-color);
+    }
+}
+
+.ignite-popper__tooltip {
+    --ignite-popper-background-color: white;
+    --ignite-popper-border-color: #ccc;
+    --ignite-popper-font-color: inherit;
+}
+
+.ignite-popper__error {
+    --ignite-popper-background-color: var(--error-red);
+    --ignite-popper-border-color: var(--error-red);
+    --ignite-popper-font-color: white;
+}
diff --git a/modules/web-console/frontend/app/components/page-profile/style.scss b/modules/web-console/frontend/app-angular/upgrade.ts
similarity index 56%
rename from modules/web-console/frontend/app/components/page-profile/style.scss
rename to modules/web-console/frontend/app-angular/upgrade.ts
index a64c892..84d0d83 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app-angular/upgrade.ts
@@ -14,20 +14,23 @@
  * limitations under the License.
  */
 
-page-profile {
-    max-width: 800px;
-    display: block;
+import {UpgradeComponent} from '@angular/upgrade/static';
+import {Directive, ElementRef, Injector, Inject, Input} from '@angular/core';
 
-    panel-collapsible {
-        width: 100%;
-    }
+@Directive({
+    selector: 'global-progress-line'
+})
+export class GlobalProgressLine extends UpgradeComponent {
+    static parameters = [[new Inject(ElementRef)], [new Inject(Injector)]];
 
-    footer {
-        display: flex;
-        justify-content: flex-end;
+    constructor(elRef: ElementRef, injector: Injector) {
+        super('globalProgressLine', elRef, injector);
     }
 
-    .btn-ignite + .btn-ignite {
-        margin-left: 10px;
-    }
+    @Input()
+    isLoading: boolean
 }
+
+export const upgradedComponents = [
+    GlobalProgressLine
+];
diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.ts
similarity index 97%
rename from modules/web-console/frontend/app/app.js
rename to modules/web-console/frontend/app/app.ts
index 7474dfc..756571f 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.ts
@@ -135,7 +135,6 @@ import igniteChartSelector from './components/ignite-chart-series-selector';
 import statusOutput from './components/status-output';
 import timedRedirection from './components/timed-redirection';
 
-import pageProfile from './components/page-profile';
 import pagePasswordChanged from './components/page-password-changed';
 import pagePasswordReset from './components/page-password-reset';
 import pageSignup from './components/page-signup';
@@ -153,6 +152,9 @@ import igniteServices from './services';
 import baseTemplate from 'views/base.pug';
 import * as icons from '../public/images/icons';
 
+import uiRouter from '@uirouter/angularjs';
+import {upgradeModule} from '@uirouter/angular-hybrid';
+
 export default angular
     .module('ignite-console', [
         // Optional AngularJS modules.
@@ -174,7 +176,8 @@ export default angular
         'ui.grid.resizeColumns',
         'ui.grid.saveState',
         'ui.grid.selection',
-        'ui.router',
+        uiRouter,
+        upgradeModule.name,
         // Base modules.
         'ignite-console.core',
         'ignite-console.ace',
@@ -227,7 +230,6 @@ export default angular
         connectedClustersDialog.name,
         igniteListOfRegisteredUsers.name,
         dialogAdminCreateUser.name,
-        pageProfile.name,
         pageLanding.name,
         pagePasswordChanged.name,
         pagePasswordReset.name,
@@ -250,6 +252,8 @@ export default angular
         noDataCmp.name,
         globalProgressBar.name
     ])
+    // Routing should wait until Angular loads. Angular app part will start it back using serviceBootstrap component.
+    .config(['$urlServiceProvider', ($urlService) => $urlService.deferIntercept()])
     .service('$exceptionHandler', $exceptionHandler)
     // Directives.
     .directive('igniteAutoFocus', igniteAutoFocus)
@@ -371,7 +375,10 @@ export default angular
         /**
          * @param {ng.IRootScopeService} $root
          * @param {ng.IHttpService} $http
+         * @param $state
          * @param {ReturnType<typeof import('./services/Messages.service').default>} Messages
+         * @param User
+         * @param Notebook
          */
         ($root, $http, $state, Messages, User, Notebook) => { // eslint-disable-line no-shadow
             $root.revertIdentity = () => {
diff --git a/modules/web-console/frontend/app/components/form-field/showValidationError.directive.ts b/modules/web-console/frontend/app/components/form-field/showValidationError.directive.ts
index 1e1e1ab..80bc7e6 100644
--- a/modules/web-console/frontend/app/components/form-field/showValidationError.directive.ts
+++ b/modules/web-console/frontend/app/components/form-field/showValidationError.directive.ts
@@ -22,7 +22,8 @@ const scrollIntoView = (() => {
     return (el: HTMLElement) => {
         try {
             el.scrollIntoView({block: 'center'});
-        } catch (e) {
+        }
+        catch (ignored) {
             el.scrollIntoView();
         }
     };
diff --git a/modules/web-console/frontend/app/components/page-profile/controller.js b/modules/web-console/frontend/app/components/page-profile/controller.js
deleted file mode 100644
index 088bfa6..0000000
--- a/modules/web-console/frontend/app/components/page-profile/controller.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
- * Licensed under the GridGain Community Edition License (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * 
- *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
- * 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 _ from 'lodash';
-
-export default class PageProfileController {
-    static $inject = [
-        '$rootScope', '$scope', '$http', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteFocus', 'IgniteConfirm', 'IgniteCountries', 'User', 'IgniteFormUtils'
-    ];
-
-    /**
-     * @param {ng.IRootScopeService} $root
-     * @param {ng.IScope} $scope
-     * @param {ng.IHttpService} $http
-     * @param {ReturnType<typeof import('app/services/LegacyUtils.service').default>} LegacyUtils
-     * @param {ReturnType<typeof import('app/services/Messages.service').default>} Messages
-     * @param {ReturnType<typeof import('app/services/Focus.service').default>} Focus
-     * @param {import('app/services/Confirm.service').Confirm} Confirm
-     * @param {ReturnType<typeof import('app/services/Countries.service').default>} Countries
-     * @param {ReturnType<typeof import('app/modules/user/User.service').default>} User
-     * @param {ReturnType<typeof import('app/services/FormUtils.service').default>} FormUtils
-     */
-    constructor($root, $scope, $http, LegacyUtils, Messages, Focus, Confirm, Countries, User, FormUtils) {
-        this.$root = $root;
-        this.$scope = $scope;
-        this.$http = $http;
-        this.LegacyUtils = LegacyUtils;
-        this.Messages = Messages;
-        this.Focus = Focus;
-        this.Confirm = Confirm;
-        this.Countries = Countries;
-        this.User = User;
-        this.FormUtils = FormUtils;
-
-        this.isLoading = false;
-    }
-
-    $onInit() {
-        this.ui = {};
-
-        this.User.read()
-            .then((user) => this.ui.user = _.cloneDeep(user));
-
-        this.ui.countries = this.Countries.getAll();
-    }
-
-    onSecurityTokenPanelClose() {
-        this.ui.user.token = this.$root.user.token;
-    }
-
-    generateToken() {
-        this.Confirm.confirm('Are you sure you want to change security token?<br>If you change the token you will need to restart the agent.')
-            .then(() => this.ui.user.token = this.LegacyUtils.randomString(20));
-    }
-
-    onPasswordPanelClose() {
-        delete this.ui.user.password;
-        delete this.ui.user.confirm;
-    }
-
-    saveUser() {
-        if (this.form.$invalid) {
-            this.FormUtils.triggerValidation(this.form);
-
-            return;
-        }
-
-        this.isLoading = true;
-
-        return this.$http.post('/api/v1/profile/save', this.ui.user)
-            .then(this.User.load)
-            .then(() => {
-                this.ui.expandedPassword = this.ui.expandedToken = false;
-
-                this.Messages.showInfo('Profile saved.');
-
-                this.Focus.move('profile-username');
-
-                this.$root.$broadcast('user', this.ui.user);
-            })
-            .catch((res) => this.Messages.showError('Failed to save profile: ', res))
-            .finally(() => this.isLoading = false);
-    }
-}
diff --git a/modules/web-console/frontend/app/components/page-profile/template.pug b/modules/web-console/frontend/app/components/page-profile/template.pug
deleted file mode 100644
index e307b12..0000000
--- a/modules/web-console/frontend/app/components/page-profile/template.pug
+++ /dev/null
@@ -1,161 +0,0 @@
-//-
-    Copyright 2019 GridGain Systems, Inc. and Contributors.
-    
-    Licensed under the GridGain Community Edition License (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-    
-        https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
-    
-    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.
-
-
-include /app/helpers/jade/mixins
-
-div
-    global-progress-line(is-loading='$ctrl.isLoading')
-
-    header.header-with-selector
-        div
-            h1 User profile
-
-    -var form = '$ctrl.form'
-    form.theme--ignite(name='$ctrl.form' novalidate)
-        .row
-            .col-50
-                +form-field__text({
-                    label: 'First name:',
-                    model: '$ctrl.ui.user.firstName',
-                    name: '"firstName"',
-                    required: true,
-                    placeholder: 'Input first name'
-                })(
-                ignite-auto-focus
-                    ignite-on-enter-focus-move='lastNameInput'
-                )
-            .col-50
-                +form-field__text({
-                    label: 'Last name:',
-                    model: '$ctrl.ui.user.lastName',
-                    name: '"lastName"',
-                    required: true,
-                    placeholder: 'Input last name'
-                })(
-                    ignite-on-enter-focus-move='emailInput'
-                )
-        .row
-            .col-100
-                +form-field__email({
-                    label: 'Email:',
-                    model: '$ctrl.ui.user.email',
-                    name: '"email"',
-                    required: true,
-                    placeholder: 'Input email'
-                })(
-                    ignite-on-enter-focus-move='phoneInput'
-                )
-        .row
-            .col-50
-                +form-field__phone({
-                    label: 'Phone:',
-                    model: '$ctrl.ui.user.phone',
-                    name: '"phone"',
-                    optional: true,
-                    placeholder: 'Input phone (ex.: +15417543010)'
-                })(
-                    ignite-on-enter-focus-move='companyInput'
-                )
-            .col-50
-                +form-field__dropdown({
-                    label: 'Country/Region:',
-                    model: '$ctrl.ui.user.country',
-                    name: '"country"',
-                    required: true,
-                    placeholder: 'Choose your country/region',
-                    options: '$ctrl.ui.countries'
-                })
-        .row
-            .col-100
-                +form-field__text({
-                    label: 'Company:',
-                    model: '$ctrl.ui.user.company',
-                    name: '"company"',
-                    required: true,
-                    placeholder: 'Input company name'
-                })(
-                    ignite-on-enter-focus-move='countryInput'
-                )
-
-        .row#security-token-section
-            .col-100
-                panel-collapsible(
-                    opened='$ctrl.ui.expandedToken'
-                    on-open='$ctrl.ui.expandedToken = true'
-                    on-close='$ctrl.onSecurityTokenPanelClose()'
-                )
-                    panel-title
-                        | {{ $panel.opened ? 'Cancel security token changing...' : 'Show security token...' }}
-                    panel-content
-                        .row
-                            .col-50
-                                +form-field__text({
-                                    label: 'Security Token:',
-                                    model: '$ctrl.ui.user.token',
-                                    tip: 'The security token is used for authentication of Web agent',
-                                    name: '"securityToken"',
-                                    placeholder: 'No security token. Regenerate please.'
-                                })(
-                                    autocomplete='security-token'
-                                    ng-disabled='::true'
-                                    copy-input-value-button='Copy security token to clipboard'
-                                )
-                            .col-50
-                                a(ng-click='$ctrl.generateToken()') Generate Random Security Token?
-
-        .row
-            .col-100
-                panel-collapsible(
-                    opened='$ctrl.ui.expandedPassword'
-                    on-open='$ctrl.ui.expandedPassword = true'
-                    on-close='$ctrl.onPasswordPanelClose()'
-                )
-                    panel-title
-                        | {{ $panel.opened ? 'Cancel password changing...' : 'Change password...' }}
-                    panel-content(ng-if='$panel.opened')
-                        .row
-                            .col-100
-                                +form-field__password({
-                                    label: 'New password:',
-                                    model: '$ctrl.ui.user.password',
-                                    name: '"password"',
-                                    required: true,
-                                    placeholder: 'New password'
-                                })(
-                                    ignite-auto-focus
-                                    ignite-on-enter-focus-move='passwordConfirmInput'
-                                )
-
-                        .row
-                            .col-100
-                                +form-field__password({
-                                    label: 'Confirm password:',
-                                    model: 'user.confirm',
-                                    name: '"passwordConfirm"',
-                                    required: true,
-                                    placeholder: 'Confirm new password'
-                                })(
-                                    ignite-on-enter-focus-move='passwordConfirmInput'
-                                    ignite-match='$ctrl.ui.user.password'
-                                )
-
-    hr
-
-    footer
-        a.btn-ignite.btn-ignite--link-success(type='button' ui-sref='default-state') Cancel
-        button.btn-ignite.btn-ignite--success(ng-click='$ctrl.saveUser()' ng-disabled='$ctrl.isLoading')
-            svg.icon-left(ignite-icon='checkmark')
-            | Save Changes
diff --git a/modules/web-console/frontend/app/components/page-signin/run.ts b/modules/web-console/frontend/app/components/page-signin/run.ts
index c66c990..7590c55 100644
--- a/modules/web-console/frontend/app/components/page-signin/run.ts
+++ b/modules/web-console/frontend/app/components/page-signin/run.ts
@@ -47,7 +47,8 @@ export function registerState($uiRouter: UIRouter) {
                         const restored = trans.router.stateService.target(name, params);
 
                         return restored.valid() ? restored : 'default-state';
-                    } catch (ignored) {
+                    }
+                    catch (ignored) {
                         return 'default-state';
                     }
                 })
diff --git a/modules/web-console/frontend/app/configuration/components/modal-import-models/component.js b/modules/web-console/frontend/app/configuration/components/modal-import-models/component.js
index 722c6b7..d7553f6 100644
--- a/modules/web-console/frontend/app/configuration/components/modal-import-models/component.js
+++ b/modules/web-console/frontend/app/configuration/components/modal-import-models/component.js
@@ -424,7 +424,7 @@ export class ModalImportModels {
                     }
                 });
             }
-            catch (ignore) {
+            catch (ignored) {
                 // No-op.
             }
         }
diff --git a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
index 7a0185b..2081c04 100644
--- a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
+++ b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
@@ -175,7 +175,7 @@ export default class AgentManager {
         try {
             return JSON.parse(localStorage.cluster);
         }
-        catch (ignore) {
+        catch (ignored) {
             return null;
         }
         finally {
@@ -284,7 +284,7 @@ export default class AgentManager {
         try {
             localStorage.cluster = JSON.stringify(cluster);
         }
-        catch (ignore) {
+        catch (ignored) {
             // No-op.
         }
     }
diff --git a/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js b/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
index 315f97f..8bc019e 100644
--- a/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
+++ b/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
@@ -106,7 +106,7 @@ export function service($root, $modal, igniteGettingStarted) {
         try {
             localStorage.showGettingStarted = !scope.ui.dontShowGettingStarted;
         }
-        catch (ignore) {
+        catch (ignored) {
             // No-op.
         }
 
@@ -122,7 +122,7 @@ export function service($root, $modal, igniteGettingStarted) {
                 scope.ui.dontShowGettingStarted = !(_.isNil(localStorage.showGettingStarted)
                         || localStorage.showGettingStarted === 'true');
             }
-            catch (ignore) {
+            catch (ignored) {
                 // No-op.
             }
 
diff --git a/modules/web-console/frontend/app/modules/user/Auth.service.ts b/modules/web-console/frontend/app/modules/user/Auth.service.ts
index 8481b85..c066663 100644
--- a/modules/web-console/frontend/app/modules/user/Auth.service.ts
+++ b/modules/web-console/frontend/app/modules/user/Auth.service.ts
@@ -90,8 +90,9 @@ export default class AuthService {
     async resendSignupConfirmation(email: string) {
         try {
             return await this.$http.post('/api/v1/activation/resend/', {email});
-        } catch (res) {
-            throw res.data;
+        }
+        catch (err) {
+            throw err.data;
         }
     }
 }
diff --git a/modules/web-console/frontend/app/modules/user/User.service.js b/modules/web-console/frontend/app/modules/user/User.service.ts
similarity index 58%
rename from modules/web-console/frontend/app/modules/user/User.service.js
rename to modules/web-console/frontend/app/modules/user/User.service.ts
index 60a70da..aa47d3a 100644
--- a/modules/web-console/frontend/app/modules/user/User.service.js
+++ b/modules/web-console/frontend/app/modules/user/User.service.ts
@@ -15,31 +15,35 @@
  */
 
 import {ReplaySubject} from 'rxjs';
+import {StateService} from '@uirouter/angularjs';
+import {default as MessagesFactory} from 'app/services/Messages.service';
 
-/**
- * @typedef User
- * @prop {string} _id
- * @prop {boolean} admin
- * @prop {string} country
- * @prop {string} email
- * @prop {string} firstName
- * @prop {string} lastName
- * @prop {string} lastActivity
- * @prop {string} lastLogin
- * @prop {string} registered
- * @prop {string} token
- */
+export type User = {
+    _id: string,
+    admin: boolean,
+    country: string,
+    email: string,
+    phone?: string,
+    company?: string,
+    firstName: string,
+    lastName: string,
+    lastActivity: string,
+    lastLogin: string,
+    registered: string,
+    token: string
+}
 
-/**
- * @param {ng.IQService} $q
- * @param {ng.auto.IInjectorService} $injector
- * @param {ng.IRootScopeService} $root
- * @param {import('@uirouter/angularjs').StateService} $state
- * @param {ng.IHttpService} $http
- */
-export default function User($q, $injector, $root, $state, $http) {
-    /** @type {ng.IPromise<User>} */
-    let user;
+User.$inject = ['$q', '$injector', '$rootScope', '$state', '$http', 'IgniteMessages'];
+
+export default function User(
+    $q: ng.IQService,
+    $injector: ng.auto.IInjectorService,
+    $root: ng.IRootScopeService,
+    $state: StateService,
+    $http: ng.IHttpService,
+    IgniteMessages: ReturnType<typeof MessagesFactory>
+) {
+    let user: ng.IPromise<User>;
 
     const current$ = new ReplaySubject(1);
 
@@ -77,8 +81,19 @@ export default function User($q, $injector, $root, $state, $http) {
             delete $root.IgniteDemoMode;
 
             sessionStorage.removeItem('IgniteDemoMode');
+        },
+
+        async save(user: Partial<User>): Promise<User> {
+            try {
+                const {data: updatedUser} = await $http.post<User>('/api/v1/profile/save', user);
+                await this.load();
+                IgniteMessages.showInfo('Profile saved.');
+                $root.$broadcast('user', updatedUser);
+                return updatedUser;
+            }
+            catch (e) {
+                IgniteMessages.showError('Failed to save profile: ', e);
+            }
         }
     };
 }
-
-User.$inject = ['$q', '$injector', '$rootScope', '$state', '$http'];
diff --git a/modules/web-console/frontend/app/vendor.js b/modules/web-console/frontend/app/vendor.js
index aa12b34..377c738 100644
--- a/modules/web-console/frontend/app/vendor.js
+++ b/modules/web-console/frontend/app/vendor.js
@@ -24,7 +24,9 @@ import 'angular-strap/dist/angular-strap.tpl';
 import 'angular1-async-filter';
 
 import 'angular-messages';
-import '@uirouter/angularjs';
+
+import 'core-js/es7/reflect';
+import 'zone.js/dist/zone';
 
 import 'resize-observer-polyfill';
 
diff --git a/modules/web-console/frontend/index.js b/modules/web-console/frontend/index.js
index d1bb4b8..c317247 100644
--- a/modules/web-console/frontend/index.js
+++ b/modules/web-console/frontend/index.js
@@ -18,5 +18,7 @@ import angular from 'angular';
 
 import igniteConsole from './app/app';
 import configurationLazyModule from './app/configuration/index.lazy';
+import {IgniteWebConsoleModule} from './app-angular';
+import {downgradeModuleFactory} from './app-angular/downgrade';
 
-angular.bootstrap(document, [igniteConsole.name, configurationLazyModule.name], {strictDi: true});
+angular.bootstrap(document, [igniteConsole.name, configurationLazyModule.name, downgradeModuleFactory(IgniteWebConsoleModule)], {strictDi: true});
diff --git a/modules/web-console/frontend/package-lock.json b/modules/web-console/frontend/package-lock.json
index 459e517..4e4f928 100644
--- a/modules/web-console/frontend/package-lock.json
+++ b/modules/web-console/frontend/package-lock.json
@@ -4,6 +4,76 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@angular/common": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.2.tgz",
+      "integrity": "sha512-43EcR3mbM+dKH4VE1EYS1HxSuEToxxv5XPktKqdzY95g8PBOxe11ifcXoYHgImd7YOWzcKoy0k6yQbX3o0cZ8g==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/compiler": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.2.tgz",
+      "integrity": "sha512-vjPreOVPca6HSuDmj7N1w5u8hwXdm98gEPo2wqQMVuJd6qvGEyLYE9FsHc0XCchyQEKSybAYl1dwsjZq2nNSvQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/core": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.2.tgz",
+      "integrity": "sha512-kQ0HxUYAPvly8b3aibTGbiodFnBBgo3asXAQuPgFjYYEqcKR1zZII7PQdaEF9kb9sfm/IKLKj4nd9fZ0gcgqZg==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/forms": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.2.tgz",
+      "integrity": "sha512-IsvVuUnzIA2ryRmh7l42AANPZFSyNcwqZNtxbmRq2wm3Lfed64U0rsRWWNqipjz7QTxZ2SRdAlP+XDgzg8hvMQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/language-service": {
+      "version": "7.2.4",
+      "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.4.tgz",
+      "integrity": "sha512-A9Rud/27hHMSUUjpgn57nVeLsoYgdvFwJhtlZA/oCuSpmlD+LqqBsEpPhivwn++u44+DSrFXsic29jlFnsBotw==",
+      "dev": true
+    },
+    "@angular/platform-browser": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.2.tgz",
+      "integrity": "sha512-eiaqHq26PVASx1kTngBDkFkXhaJzEjoGtc5I+wQUef8CUjq6ZViWz8tUgiiDPOWdqUKUacRZG4q6VR/6uwQj0A==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/platform-browser-dynamic": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.2.tgz",
+      "integrity": "sha512-bw5PuzMzjKMecB4slG/btmvxgn4qFWhNmJVpf2pbtZW7NtZz3HlrqipYzMk9XrCUDGjtwy7O2Z71C3ujI748iw==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/router": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.2.tgz",
+      "integrity": "sha512-+cBC+JxbPdjk+Nyqq27PKkjfIdnc+H+xjMGrkO6dlAKhVMGxyNaYt5NUNugb8XJPsQ1XNXyzwTfZK6jcAGLw6w==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/upgrade": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/upgrade/-/upgrade-7.2.2.tgz",
+      "integrity": "sha512-eKqJuAgu3vce0oHB7BxF4imprvWfLmJbEB2sXtR1ZjeCR/c4WP7CwDaLA5GBKadsJ7oWrS+N7U3Ay+CFJ64pUQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
     "@babel/code-frame": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
@@ -86,6 +156,19 @@
         "@babel/types": "^7.0.0"
       }
     },
+    "@babel/helper-create-class-features-plugin": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.0.tgz",
+      "integrity": "sha512-DUsQNS2CGLZZ7I3W3fvh0YpPDd6BuWJlDl+qmZZpABZHza2ErE3LxtEzLJFHFC1ZwtlAXvHhbFYbtM5o5B0WBw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.1.0",
+        "@babel/helper-member-expression-to-functions": "^7.0.0",
+        "@babel/helper-optimise-call-expression": "^7.0.0",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-replace-supers": "^7.2.3"
+      }
+    },
     "@babel/helper-define-map": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz",
@@ -294,6 +377,17 @@
         "@babel/plugin-syntax-class-properties": "^7.0.0"
       }
     },
+    "@babel/plugin-proposal-decorators": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.3.0.tgz",
+      "integrity": "sha512-3W/oCUmsO43FmZIqermmq6TKaRSYhmh/vybPfVFwQWdSb8xwki38uAIvknCRzuyHRuYfCYmJzL9or1v0AffPjg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-create-class-features-plugin": "^7.3.0",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-decorators": "^7.2.0"
+      }
+    },
     "@babel/plugin-proposal-json-strings": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
@@ -353,6 +447,15 @@
         "@babel/helper-plugin-utils": "^7.0.0"
       }
     },
+    "@babel/plugin-syntax-decorators": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz",
+      "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
     "@babel/plugin-syntax-dynamic-import": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz",
@@ -1049,25 +1152,37 @@
         }
       }
     },
+    "@uirouter/angular": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@uirouter/angular/-/angular-3.0.0.tgz",
+      "integrity": "sha512-HgAl0tk+RxuspC6vSNINMlzjTJgo+TFSpJ37OQgY7WEU7OrEDCdftVITJHyU/J87UES1ajalJljeq0/fI8Sv7g==",
+      "requires": {
+        "@uirouter/core": "5.0.23",
+        "@uirouter/rx": "0.5.0"
+      }
+    },
+    "@uirouter/angular-hybrid": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@uirouter/angular-hybrid/-/angular-hybrid-7.0.0.tgz",
+      "integrity": "sha512-9/lpRJhU6mCBsxl1tWUtbYVTeYgjTm1kprd8AOyqQ2SElIX63CZDg4I90CDDFJm4M70QLMHCNW8zZmlhglPufg==",
+      "requires": {
+        "@uirouter/angular": "3.0.0",
+        "@uirouter/angularjs": "1.0.22",
+        "@uirouter/core": "5.0.23"
+      }
+    },
     "@uirouter/angularjs": {
-      "version": "1.0.20",
-      "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.20.tgz",
-      "integrity": "sha512-fY6bsesTL/tg8gyFHXaIjD1r5b7Ac+SYupodO9OzT4/gKI0YC+uGzLpQESAiXlT3fsxdEPVBzdtAbzXDwCKdEA==",
+      "version": "1.0.22",
+      "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.22.tgz",
+      "integrity": "sha512-d0SvdbXAav+Z6gCJd7gmn2eXEUtO3RvYcSLwtPaE8+7QiWHpSKNfGdD4D3noXhO2yUTz/AwaxsiRFMCwgVI0UQ==",
       "requires": {
-        "@uirouter/core": "5.0.21"
-      },
-      "dependencies": {
-        "@uirouter/core": {
-          "version": "5.0.21",
-          "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-5.0.21.tgz",
-          "integrity": "sha512-QWHc0wT00qtYNkT0BXZaFNLLHZyux0qJjF8c2WklW5/Q0Z866NoJjJErEMWjQb/AR+olkR2NlfEEi8KTQU2OpA=="
-        }
+        "@uirouter/core": "5.0.23"
       }
     },
     "@uirouter/core": {
-      "version": "5.0.19",
-      "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-5.0.19.tgz",
-      "integrity": "sha512-wow+CKRThUAQkiTLNQCBsKQIU3NbH8GGH/w/TrcjKdvkZQA2jQB9QSqmmZxj7XNoZXY7QVcSSc4DWmxuSeAWmQ=="
+      "version": "5.0.23",
+      "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-5.0.23.tgz",
+      "integrity": "sha512-rwFOH++z/KY8y+h0IOpQ5uC8Nim6E0EBCQrIjhVCr+XKYXgpK+VdtuOLFdogvbJ3AAi5Z7ei00qdEr7Did5CAg=="
     },
     "@uirouter/rx": {
       "version": "0.5.0",
@@ -1266,8 +1381,7 @@
     "abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
-      "dev": true
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
     },
     "accepts": {
       "version": "1.3.5",
@@ -1418,9 +1532,9 @@
       "dev": true
     },
     "angular": {
-      "version": "1.7.6",
-      "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.6.tgz",
-      "integrity": "sha512-QELpvuMIe1FTGniAkRz93O6A+di0yu88niDwcdzrSqtUHNtZMgtgFS4f7W/6Gugbuwej8Kyswlmymwdp8iPCWg=="
+      "version": "1.7.7",
+      "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.7.tgz",
+      "integrity": "sha512-MH3JEGd8y/EkNCKJ8EV6Ch0j9X0rZTta/QVIDpBWaIdfh85/e5KO8+ZKgvWIb02MQuiS20pDFmMFlv4ZaLcLWg=="
     },
     "angular-acl": {
       "version": "0.1.10",
@@ -1616,12 +1730,6 @@
       "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
       "dev": true
     },
-    "array-slice": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
-      "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=",
-      "dev": true
-    },
     "array-union": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -1924,8 +2032,7 @@
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-      "dev": true
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
     },
     "base": {
       "version": "0.11.2",
@@ -2144,7 +2251,6 @@
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
       "requires": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -2577,9 +2683,9 @@
       "dev": true
     },
     "chokidar": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz",
-      "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==",
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz",
+      "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==",
       "dev": true,
       "requires": {
         "anymatch": "^2.0.0",
@@ -2593,7 +2699,7 @@
         "normalize-path": "^3.0.0",
         "path-is-absolute": "^1.0.0",
         "readdirp": "^2.2.1",
-        "upath": "^1.1.0"
+        "upath": "^1.1.1"
       },
       "dependencies": {
         "normalize-path": {
@@ -2601,14 +2707,19 @@
           "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
           "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
           "dev": true
+        },
+        "upath": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
+          "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
+          "dev": true
         }
       }
     },
     "chownr": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
-      "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
-      "dev": true
+      "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
     },
     "chrome-trace-event": {
       "version": "1.0.0",
@@ -2908,15 +3019,6 @@
       "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
       "dev": true
     },
-    "combine-lists": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz",
-      "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=",
-      "dev": true,
-      "requires": {
-        "lodash": "^4.5.0"
-      }
-    },
     "combined-stream": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
@@ -2963,24 +3065,24 @@
       },
       "dependencies": {
         "mime-db": {
-          "version": "1.38.0",
-          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
-          "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==",
+          "version": "1.39.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.39.0.tgz",
+          "integrity": "sha512-DTsrw/iWVvwHH+9Otxccdyy0Tgiil6TWK/xhfARJZF/QFhwOgZgOIvA2/VIGpM8U7Q8z5nDmdDWC6tuVMJNibw==",
           "dev": true
         }
       }
     },
     "compression": {
-      "version": "1.7.3",
-      "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
-      "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+      "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
       "dev": true,
       "requires": {
         "accepts": "~1.3.5",
         "bytes": "3.0.0",
-        "compressible": "~2.0.14",
+        "compressible": "~2.0.16",
         "debug": "2.6.9",
-        "on-headers": "~1.0.1",
+        "on-headers": "~1.0.2",
         "safe-buffer": "5.1.2",
         "vary": "~1.1.2"
       },
@@ -3005,8 +3107,7 @@
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-      "dev": true
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
     },
     "concat-stream": {
       "version": "1.6.2",
@@ -3208,8 +3309,7 @@
     "core-js": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.2.tgz",
-      "integrity": "sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g==",
-      "dev": true
+      "integrity": "sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g=="
     },
     "core-util-is": {
       "version": "1.0.2",
@@ -3508,9 +3608,9 @@
       }
     },
     "date-format": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
-      "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz",
+      "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==",
       "dev": true
     },
     "date-now": {
@@ -3562,6 +3662,13 @@
       "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
       "dev": true
     },
+    "deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true,
+      "optional": true
+    },
     "deep-is": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -3575,9 +3682,9 @@
       "dev": true
     },
     "default-gateway": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.0.1.tgz",
-      "integrity": "sha512-JnSsMUgrBFy9ycs+tmOvLHN1GpILe+hNSUrIVM8mXjymfcBH9a7LJjOdoHLuUqKGuCUk6mSIPJjZ11Zszrg3oQ==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
+      "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==",
       "dev": true,
       "requires": {
         "execa": "^1.0.0",
@@ -3641,17 +3748,17 @@
       "dev": true
     },
     "del": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
-      "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/del/-/del-4.1.0.tgz",
+      "integrity": "sha512-C4kvKNlYrwXhKxz97BuohF8YoGgQ23Xm9lvoHmgT7JaPGprSEjk3+XFled74Yt/x0ZABUHg2D67covzAPUKx5Q==",
       "dev": true,
       "requires": {
         "globby": "^6.1.0",
-        "is-path-cwd": "^1.0.0",
-        "is-path-in-cwd": "^1.0.0",
-        "p-map": "^1.1.1",
-        "pify": "^3.0.0",
-        "rimraf": "^2.2.8"
+        "is-path-cwd": "^2.0.0",
+        "is-path-in-cwd": "^2.0.0",
+        "p-map": "^2.0.0",
+        "pify": "^4.0.1",
+        "rimraf": "^2.6.3"
       },
       "dependencies": {
         "globby": {
@@ -3674,6 +3781,12 @@
               "dev": true
             }
           }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
         }
       }
     },
@@ -3711,11 +3824,12 @@
       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
       "dev": true
     },
-    "detect-file": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
-      "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
-      "dev": true
+    "detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
+      "dev": true,
+      "optional": true
     },
     "detect-node": {
       "version": "2.0.4",
@@ -3920,6 +4034,12 @@
         "minimalistic-crypto-utils": "^1.0.0"
       }
     },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
     "emojis-list": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -4415,34 +4535,6 @@
         }
       }
     },
-    "expand-braces": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz",
-      "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=",
-      "dev": true,
-      "requires": {
-        "array-slice": "^0.2.3",
-        "array-unique": "^0.2.1",
-        "braces": "^0.1.2"
-      },
-      "dependencies": {
-        "array-unique": {
-          "version": "0.2.1",
-          "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
-          "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
-          "dev": true
-        },
-        "braces": {
-          "version": "0.1.5",
-          "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz",
-          "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=",
-          "dev": true,
-          "requires": {
-            "expand-range": "^0.1.0"
-          }
-        }
-      }
-    },
     "expand-brackets": {
       "version": "2.1.4",
       "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -4493,39 +4585,6 @@
         }
       }
     },
-    "expand-range": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz",
-      "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=",
-      "dev": true,
-      "requires": {
-        "is-number": "^0.1.1",
-        "repeat-string": "^0.2.2"
-      },
-      "dependencies": {
-        "is-number": {
-          "version": "0.1.1",
-          "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz",
-          "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=",
-          "dev": true
-        },
-        "repeat-string": {
-          "version": "0.2.2",
-          "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz",
-          "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=",
-          "dev": true
-        }
-      }
-    },
-    "expand-tilde": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
-      "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
-      "dev": true,
-      "requires": {
-        "homedir-polyfill": "^1.0.1"
-      }
-    },
     "expose-loader": {
       "version": "0.7.5",
       "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.5.tgz",
@@ -4891,29 +4950,6 @@
         "locate-path": "^2.0.0"
       }
     },
-    "findup-sync": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
-      "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
-      "dev": true,
-      "requires": {
-        "detect-file": "^1.0.0",
-        "is-glob": "^3.1.0",
-        "micromatch": "^3.0.4",
-        "resolve-dir": "^1.0.1"
-      },
-      "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.0"
-          }
-        }
-      }
-    },
     "flat": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
@@ -5062,6 +5098,25 @@
         "null-check": "^1.0.0"
       }
     },
+    "fs-extra": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+      "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
+    "fs-minipass": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
+      "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
+    },
     "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",
@@ -5077,8 +5132,7 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-      "dev": true
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
     "fsevents": {
       "version": "1.2.7",
@@ -5091,137 +5145,23 @@
         "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.5",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "delegates": "^1.0.0",
-            "readable-stream": "^2.0.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.1.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
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
         },
         "debug": {
           "version": "2.6.9",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
           "requires": {
             "ms": "2.0.0"
           }
         },
-        "deep-extend": {
-          "version": "0.6.0",
-          "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.1"
-          }
-        },
-        "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.0.3",
-            "console-control-strings": "^1.0.0",
-            "has-unicode": "^2.0.0",
-            "object-assign": "^4.1.0",
-            "signal-exit": "^3.0.0",
-            "string-width": "^1.0.1",
-            "strip-ansi": "^3.0.1",
-            "wide-align": "^1.1.0"
-          }
-        },
         "glob": {
           "version": "7.1.3",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+          "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
           "requires": {
             "fs.realpath": "^1.0.0",
             "inflight": "^1.0.4",
@@ -5231,255 +5171,42 @@
             "path-is-absolute": "^1.0.0"
           }
         },
-        "has-unicode": {
-          "version": "2.0.1",
-          "bundled": true,
-          "dev": true,
-          "optional": true
-        },
-        "iconv-lite": {
-          "version": "0.4.24",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "safer-buffer": ">= 2.1.2 < 3"
-          }
-        },
-        "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.3.0",
-            "wrappy": "1"
-          }
-        },
-        "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,
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "requires": {
             "number-is-nan": "^1.0.0"
           }
         },
-        "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.7"
-          }
-        },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true,
-          "dev": true
-        },
-        "minipass": {
-          "version": "2.3.5",
-          "bundled": true,
-          "dev": true,
-          "requires": {
-            "safe-buffer": "^5.1.2",
-            "yallist": "^3.0.0"
-          }
-        },
-        "minizlib": {
-          "version": "1.2.1",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "minipass": "^2.2.1"
-          }
-        },
-        "mkdirp": {
-          "version": "0.5.1",
-          "bundled": true,
-          "dev": true,
-          "requires": {
-            "minimist": "0.0.8"
-          }
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         },
         "ms": {
           "version": "2.0.0",
-          "bundled": true,
-          "dev": true,
-          "optional": true
-        },
-        "needle": {
-          "version": "2.2.4",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "debug": "^2.1.2",
-            "iconv-lite": "^0.4.4",
-            "sax": "^1.2.4"
-          }
-        },
-        "node-pre-gyp": {
-          "version": "0.10.3",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "detect-libc": "^1.0.2",
-            "mkdirp": "^0.5.1",
-            "needle": "^2.2.1",
-            "nopt": "^4.0.1",
-            "npm-packlist": "^1.1.6",
-            "npmlog": "^4.0.2",
-            "rc": "^1.2.7",
-            "rimraf": "^2.6.1",
-            "semver": "^5.3.0",
-            "tar": "^4"
-          }
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
         },
         "nopt": {
           "version": "4.0.1",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
           "requires": {
             "abbrev": "1",
             "osenv": "^0.1.4"
           }
         },
-        "npm-bundled": {
-          "version": "1.0.5",
-          "bundled": true,
-          "dev": true,
-          "optional": true
-        },
-        "npm-packlist": {
-          "version": "1.2.0",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ignore-walk": "^3.0.1",
-            "npm-bundled": "^1.0.1"
-          }
-        },
-        "npmlog": {
-          "version": "4.1.2",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "are-we-there-yet": "~1.1.2",
-            "console-control-strings": "~1.1.0",
-            "gauge": "~2.7.3",
-            "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"
-          }
-        },
-        "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.0",
-            "os-tmpdir": "^1.0.0"
-          }
-        },
-        "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.8",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "deep-extend": "^0.6.0",
-            "ini": "~1.3.0",
-            "minimist": "^1.2.0",
-            "strip-json-comments": "~2.0.1"
-          },
-          "dependencies": {
-            "minimist": {
-              "version": "1.2.0",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            }
-          }
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+          "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
         },
         "readable-stream": {
           "version": "2.3.6",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "requires": {
             "core-util-is": "~1.0.0",
             "inherits": "~2.0.3",
@@ -5490,88 +5217,26 @@
             "util-deprecate": "~1.0.1"
           }
         },
-        "rimraf": {
-          "version": "2.6.3",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "glob": "^7.1.3"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "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.6.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.0.0",
-            "is-fullwidth-code-point": "^1.0.0",
-            "strip-ansi": "^3.0.0"
-          }
-        },
         "string_decoder": {
           "version": "1.1.1",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "requires": {
             "safe-buffer": "~5.1.0"
           }
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "bundled": true,
-          "dev": true,
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "requires": {
             "ansi-regex": "^2.0.0"
           }
         },
-        "strip-json-comments": {
-          "version": "2.0.1",
-          "bundled": true,
-          "dev": true,
-          "optional": true
-        },
         "tar": {
           "version": "4.4.8",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
+          "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
+          "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
           "requires": {
             "chownr": "^1.1.1",
             "fs-minipass": "^1.2.5",
@@ -5582,30 +5247,10 @@
             "yallist": "^3.0.2"
           }
         },
-        "util-deprecate": {
-          "version": "1.0.2",
-          "bundled": true,
-          "dev": true,
-          "optional": true
-        },
-        "wide-align": {
-          "version": "1.1.3",
-          "bundled": true,
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "string-width": "^1.0.2 || 2"
-          }
-        },
-        "wrappy": {
-          "version": "1.0.2",
-          "bundled": true,
-          "dev": true
-        },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true,
-          "dev": true
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
         }
       }
     },
@@ -5790,36 +5435,12 @@
       "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
       "dev": true
     },
-    "global-modules": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
-      "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
-      "dev": true,
-      "requires": {
-        "global-prefix": "^1.0.1",
-        "is-windows": "^1.0.1",
-        "resolve-dir": "^1.0.0"
-      }
-    },
     "global-modules-path": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.1.tgz",
       "integrity": "sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==",
       "dev": true
     },
-    "global-prefix": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
-      "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
-      "dev": true,
-      "requires": {
-        "expand-tilde": "^2.0.2",
-        "homedir-polyfill": "^1.0.1",
-        "ini": "^1.3.4",
-        "is-windows": "^1.0.1",
-        "which": "^1.2.14"
-      }
-    },
     "globals": {
       "version": "11.10.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz",
@@ -6035,15 +5656,6 @@
         "minimalistic-crypto-utils": "^1.0.1"
       }
     },
-    "homedir-polyfill": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
-      "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
-      "dev": true,
-      "requires": {
-        "parse-passwd": "^1.0.0"
-      }
-    },
     "hosted-git-info": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
@@ -6412,6 +6024,16 @@
       "integrity": "sha1-2B8kA3bQuk8Nd4lyw60lh0EXpGM=",
       "dev": true
     },
+    "ignore-walk": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+      "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
     "image-size": {
       "version": "0.5.5",
       "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -6479,7 +6101,6 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true,
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -6494,7 +6115,8 @@
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
       "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "inquirer": {
       "version": "6.2.1",
@@ -6535,12 +6157,12 @@
       }
     },
     "internal-ip": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.2.0.tgz",
-      "integrity": "sha512-ZY8Rk+hlvFeuMmG5uH1MXhhdeMntmIaxaInvAmzMq/SHV8rv4Kh+6GiQNNDQd0wZFrcO+FiTBo8lui/osKOyJw==",
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
+      "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==",
       "dev": true,
       "requires": {
-        "default-gateway": "^4.0.1",
+        "default-gateway": "^4.2.0",
         "ipaddr.js": "^1.9.0"
       },
       "dependencies": {
@@ -6773,15 +6395,15 @@
       }
     },
     "is-path-cwd": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
-      "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.0.0.tgz",
+      "integrity": "sha512-m5dHHzpOXEiv18JEORttBO64UgTEypx99vCxQLjbBvGhOJxnTNglYoFXxwo6AbsQb79sqqycQEHv2hWkHZAijA==",
       "dev": true
     },
     "is-path-in-cwd": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
-      "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.0.0.tgz",
+      "integrity": "sha512-6Vz5Gc9s/sDA3JBVu0FzWufm8xaBsqy1zn8Q6gmvGP6nSDMw78aS4poBNeatWjaRpTpxxLn1WOndAiOlk+qY8A==",
       "dev": true,
       "requires": {
         "is-path-inside": "^1.0.0"
@@ -7037,6 +6659,15 @@
         }
       }
     },
+    "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.6"
+      }
+    },
     "jsprim": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -7079,28 +6710,27 @@
       }
     },
     "karma": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.0.tgz",
-      "integrity": "sha512-EFoFs3F6G0BcUGPNOn/YloGOb3h09hzTguyXlg6loHlKY76qbJikkcyPk43m2kfRF65TUGda/mig29QQtyhm1g==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.1.tgz",
+      "integrity": "sha512-ind+4s03BqIXas7ZmraV3/kc5+mnqwCd+VDX1FndS6jxbt03kQKX2vXrWxNLuCjVYmhMwOZosAEKMM0a2q7w7A==",
       "dev": true,
       "requires": {
         "bluebird": "^3.3.0",
         "body-parser": "^1.16.1",
+        "braces": "^2.3.2",
         "chokidar": "^2.0.3",
         "colors": "^1.1.0",
-        "combine-lists": "^1.0.0",
         "connect": "^3.6.0",
         "core-js": "^2.2.0",
         "di": "^0.0.1",
         "dom-serialize": "^2.2.0",
-        "expand-braces": "^0.1.1",
         "flatted": "^2.0.0",
         "glob": "^7.1.1",
         "graceful-fs": "^4.1.2",
         "http-proxy": "^1.13.0",
         "isbinaryfile": "^3.0.0",
-        "lodash": "^4.17.5",
-        "log4js": "^3.0.0",
+        "lodash": "^4.17.11",
+        "log4js": "^4.0.0",
         "mime": "^2.3.1",
         "minimatch": "^3.0.2",
         "optimist": "^0.6.1",
@@ -7530,33 +7160,16 @@
       }
     },
     "log4js": {
-      "version": "3.0.6",
-      "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz",
-      "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.1.0.tgz",
+      "integrity": "sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA==",
       "dev": true,
       "requires": {
-        "circular-json": "^0.5.5",
-        "date-format": "^1.2.0",
-        "debug": "^3.1.0",
+        "date-format": "^2.0.0",
+        "debug": "^4.1.1",
+        "flatted": "^2.0.0",
         "rfdc": "^1.1.2",
-        "streamroller": "0.7.0"
-      },
-      "dependencies": {
-        "circular-json": {
-          "version": "0.5.9",
-          "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz",
-          "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==",
-          "dev": true
-        },
-        "debug": {
-          "version": "3.2.6",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
-          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        }
+        "streamroller": "^1.0.4"
       }
     },
     "loglevel": {
@@ -7784,9 +7397,9 @@
       }
     },
     "mime": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
-      "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz",
+      "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==",
       "dev": true
     },
     "mime-db": {
@@ -7874,16 +7487,39 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
-      "dev": true,
       "requires": {
         "brace-expansion": "^1.1.7"
       }
     },
-    "minimist": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
-      "dev": true
+    "minimist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+      "dev": true
+    },
+    "minipass": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+      "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+        }
+      }
+    },
+    "minizlib": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
+      "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
     },
     "mississippi": {
       "version": "2.0.0",
@@ -7952,7 +7588,6 @@
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
-      "dev": true,
       "requires": {
         "minimist": "0.0.8"
       },
@@ -7960,15 +7595,14 @@
         "minimist": {
           "version": "0.0.8",
           "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         }
       }
     },
     "mocha": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.0.1.tgz",
-      "integrity": "sha512-tQzCxWqxSD6Oyg5r7Ptbev0yAMD8p+Vfh4snPFuiUsWqYj0eVYTDT2DkEY307FTj0WRlIWN9rWMMAUzRmijgVQ==",
+      "version": "6.1.3",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.3.tgz",
+      "integrity": "sha512-QdE/w//EPHrqgT5PNRUjRVHy6IJAzAf1R8n2O8W8K2RZ+NbPfOD5cBDp+PGa2Gptep37C/TdBiaNwakppEzEbg==",
       "dev": true,
       "requires": {
         "ansi-colors": "3.2.3",
@@ -7976,23 +7610,23 @@
         "debug": "3.2.6",
         "diff": "3.5.0",
         "escape-string-regexp": "1.0.5",
-        "findup-sync": "2.0.0",
+        "find-up": "3.0.0",
         "glob": "7.1.3",
         "growl": "1.10.5",
         "he": "1.2.0",
-        "js-yaml": "3.12.0",
+        "js-yaml": "3.13.0",
         "log-symbols": "2.2.0",
         "minimatch": "3.0.4",
         "mkdirp": "0.5.1",
         "ms": "2.1.1",
-        "node-environment-flags": "1.0.4",
+        "node-environment-flags": "1.0.5",
         "object.assign": "4.1.0",
         "strip-json-comments": "2.0.1",
         "supports-color": "6.0.0",
         "which": "1.3.1",
         "wide-align": "1.1.3",
-        "yargs": "12.0.5",
-        "yargs-parser": "11.1.1",
+        "yargs": "13.2.2",
+        "yargs-parser": "13.0.0",
         "yargs-unparser": "1.5.0"
       },
       "dependencies": {
@@ -8003,9 +7637,9 @@
           "dev": true
         },
         "camelcase": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
-          "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
+          "version": "5.3.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+          "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
           "dev": true
         },
         "cliui": {
@@ -8017,6 +7651,18 @@
             "string-width": "^2.1.1",
             "strip-ansi": "^4.0.0",
             "wrap-ansi": "^2.0.0"
+          },
+          "dependencies": {
+            "string-width": {
+              "version": "2.1.1",
+              "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+              "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+              "dev": true,
+              "requires": {
+                "is-fullwidth-code-point": "^2.0.0",
+                "strip-ansi": "^4.0.0"
+              }
+            }
           }
         },
         "debug": {
@@ -8049,6 +7695,12 @@
             "locate-path": "^3.0.0"
           }
         },
+        "get-caller-file": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+          "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+          "dev": true
+        },
         "glob": {
           "version": "7.1.3",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
@@ -8070,9 +7722,9 @@
           "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==",
+          "version": "3.13.0",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz",
+          "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==",
           "dev": true,
           "requires": {
             "argparse": "^1.0.7",
@@ -8110,9 +7762,9 @@
           }
         },
         "p-limit": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
-          "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+          "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
           "dev": true,
           "requires": {
             "p-try": "^2.0.0"
@@ -8128,11 +7780,45 @@
           }
         },
         "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        },
+        "require-main-filename": {
           "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
-          "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+          "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
           "dev": true
         },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          },
+          "dependencies": {
+            "ansi-regex": {
+              "version": "4.1.0",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+              "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+              "dev": true
+            },
+            "strip-ansi": {
+              "version": "5.2.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+              "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "^4.1.0"
+              }
+            }
+          }
+        },
         "strip-ansi": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@@ -8158,29 +7844,28 @@
           "dev": true
         },
         "yargs": {
-          "version": "12.0.5",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
-          "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+          "version": "13.2.2",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
+          "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
           "dev": true,
           "requires": {
             "cliui": "^4.0.0",
-            "decamelize": "^1.2.0",
             "find-up": "^3.0.0",
-            "get-caller-file": "^1.0.1",
-            "os-locale": "^3.0.0",
+            "get-caller-file": "^2.0.1",
+            "os-locale": "^3.1.0",
             "require-directory": "^2.1.1",
-            "require-main-filename": "^1.0.1",
+            "require-main-filename": "^2.0.0",
             "set-blocking": "^2.0.0",
-            "string-width": "^2.0.0",
+            "string-width": "^3.0.0",
             "which-module": "^2.0.0",
-            "y18n": "^3.2.1 || ^4.0.0",
-            "yargs-parser": "^11.1.1"
+            "y18n": "^4.0.0",
+            "yargs-parser": "^13.0.0"
           }
         },
         "yargs-parser": {
-          "version": "11.1.1",
-          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
-          "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+          "version": "13.0.0",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz",
+          "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==",
           "dev": true,
           "requires": {
             "camelcase": "^5.0.0",
@@ -8286,6 +7971,37 @@
       "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
       "integrity": "sha1-F7CVgZiJef3a/gIB6TG6kzyWy7Q="
     },
+    "needle": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
+      "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "debug": "^2.1.2",
+        "iconv-lite": "^0.4.4",
+        "sax": "^1.2.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "optional": 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,
+          "optional": true
+        }
+      }
+    },
     "negotiator": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
@@ -8298,6 +8014,14 @@
       "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
       "dev": true
     },
+    "ngx-popper": {
+      "version": "6.0.7",
+      "resolved": "https://registry.npmjs.org/ngx-popper/-/ngx-popper-6.0.7.tgz",
+      "integrity": "sha512-1IOjp1r1We7NJdJxjOGwUhjJEzCWkbIvupJyo5wLjNG0vEp0X/KWI7SEhm2ZCrrA1zeuuWSJEti5ejSHpl0y0Q==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
     "nice-try": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -8314,12 +8038,21 @@
       }
     },
     "node-environment-flags": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.4.tgz",
-      "integrity": "sha512-M9rwCnWVLW7PX+NUWe3ejEdiLYinRpsEre9hMkU/6NS4h+EEulYaDH1gCEZ2gyXsmw+RXYDaV2JkkTNcsPDJ0Q==",
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
+      "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
       "dev": true,
       "requires": {
-        "object.getownpropertydescriptors": "^2.0.3"
+        "object.getownpropertydescriptors": "^2.0.3",
+        "semver": "^5.7.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
+          "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
+          "dev": true
+        }
       }
     },
     "node-fetch": {
@@ -8478,6 +8211,61 @@
         }
       }
     },
+    "node-pre-gyp": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz",
+      "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "detect-libc": "^1.0.2",
+        "mkdirp": "^0.5.1",
+        "needle": "^2.2.1",
+        "nopt": "^4.0.1",
+        "npm-packlist": "^1.1.6",
+        "npmlog": "^4.0.2",
+        "rc": "^1.2.7",
+        "rimraf": "^2.6.1",
+        "semver": "^5.3.0",
+        "tar": "^4"
+      },
+      "dependencies": {
+        "nopt": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "abbrev": "1",
+            "osenv": "^0.1.4"
+          }
+        },
+        "tar": {
+          "version": "4.4.8",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
+          "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "chownr": "^1.1.1",
+            "fs-minipass": "^1.2.5",
+            "minipass": "^2.3.4",
+            "minizlib": "^1.1.1",
+            "mkdirp": "^0.5.0",
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
     "node-releases": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz",
@@ -8623,6 +8411,24 @@
         "sort-keys": "^1.0.0"
       }
     },
+    "npm-bundled": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz",
+      "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==",
+      "dev": true,
+      "optional": true
+    },
+    "npm-packlist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.2.0.tgz",
+      "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1"
+      }
+    },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -8668,8 +8474,7 @@
     "number-is-nan": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-      "dev": true
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
     },
     "nvd3": {
       "version": "1.8.6",
@@ -8819,7 +8624,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true,
       "requires": {
         "wrappy": "1"
       }
@@ -8834,9 +8638,9 @@
       }
     },
     "opn": {
-      "version": "5.4.0",
-      "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz",
-      "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==",
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
+      "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
       "dev": true,
       "requires": {
         "is-wsl": "^1.1.0"
@@ -8898,8 +8702,7 @@
     "os-homedir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
-      "dev": true
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
     },
     "os-locale": {
       "version": "1.4.0",
@@ -8913,14 +8716,12 @@
     "os-tmpdir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
-      "dev": true
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
     },
     "osenv": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
       "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
-      "dev": true,
       "requires": {
         "os-homedir": "^1.0.0",
         "os-tmpdir": "^1.0.0"
@@ -8968,9 +8769,9 @@
       }
     },
     "p-map": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
-      "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+      "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
       "dev": true
     },
     "p-try": {
@@ -9067,12 +8868,6 @@
         "error-ex": "^1.2.0"
       }
     },
-    "parse-passwd": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
-      "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
-      "dev": true
-    },
     "parseqs": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
@@ -9116,8 +8911,7 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-      "dev": true
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
     },
     "path-is-inside": {
       "version": "1.0.2",
@@ -9224,6 +9018,11 @@
       "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
       "dev": true
     },
+    "popper.js": {
+      "version": "1.14.7",
+      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz",
+      "integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ=="
+    },
     "portfinder": {
       "version": "1.0.20",
       "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz",
@@ -10351,9 +10150,9 @@
       "dev": true
     },
     "querystringify": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
-      "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+      "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
       "dev": true
     },
     "randombytes": {
@@ -10404,6 +10203,19 @@
         }
       }
     },
+    "rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      }
+    },
     "read-pkg": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@@ -10842,16 +10654,6 @@
         }
       }
     },
-    "resolve-dir": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
-      "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
-      "dev": true,
-      "requires": {
-        "expand-tilde": "^2.0.0",
-        "global-modules": "^1.0.0"
-      }
-    },
     "resolve-from": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -11825,9 +11627,9 @@
       },
       "dependencies": {
         "readable-stream": {
-          "version": "3.1.1",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz",
-          "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==",
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz",
+          "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==",
           "dev": true,
           "requires": {
             "inherits": "^2.0.3",
@@ -12001,15 +11803,16 @@
       "dev": true
     },
     "streamroller": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
-      "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.4.tgz",
+      "integrity": "sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ==",
       "dev": true,
       "requires": {
-        "date-format": "^1.2.0",
+        "async": "^2.6.1",
+        "date-format": "^2.0.0",
         "debug": "^3.1.0",
-        "mkdirp": "^0.5.1",
-        "readable-stream": "^2.3.0"
+        "fs-extra": "^7.0.0",
+        "lodash": "^4.17.10"
       },
       "dependencies": {
         "debug": {
@@ -12020,36 +11823,6 @@
           "requires": {
             "ms": "^2.1.1"
           }
-        },
-        "process-nextick-args": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-          "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
-          "dev": true
-        },
-        "readable-stream": {
-          "version": "2.3.6",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
-          "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
-          "dev": true,
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "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.1"
-          }
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "dev": true,
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
         }
       }
     },
@@ -12828,6 +12601,12 @@
         "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
+    },
     "unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -12926,9 +12705,9 @@
       }
     },
     "url-parse": {
-      "version": "1.4.4",
-      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz",
-      "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==",
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.5.tgz",
+      "integrity": "sha512-4XDvC5vZRjEpjP0L4znrWeoH8P8F0XGBlfLdABi/6oV4o8xUVbTpyrxWHxkK2bT0pSIpcjdIzSoWUhlUfawCAQ==",
       "dev": true,
       "requires": {
         "querystringify": "^2.0.0",
@@ -13394,47 +13173,47 @@
       }
     },
     "webpack-dev-server": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.2.0.tgz",
-      "integrity": "sha512-CUGPLQsUBVKa/qkZl1MMo8krm30bsOHAP8jtn78gUICpT+sR3esN4Zb0TSBzOEEQJF0zHNEbwx5GHInkqcmlsA==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.3.1.tgz",
+      "integrity": "sha512-jY09LikOyGZrxVTXK0mgIq9y2IhCoJ05848dKZqX1gAGLU1YDqgpOT71+W53JH/wI4v6ky4hm+KvSyW14JEs5A==",
       "dev": true,
       "requires": {
         "ansi-html": "0.0.7",
         "bonjour": "^3.5.0",
-        "chokidar": "^2.0.0",
-        "compression": "^1.5.2",
-        "connect-history-api-fallback": "^1.3.0",
+        "chokidar": "^2.1.5",
+        "compression": "^1.7.4",
+        "connect-history-api-fallback": "^1.6.0",
         "debug": "^4.1.1",
-        "del": "^3.0.0",
-        "express": "^4.16.2",
-        "html-entities": "^1.2.0",
+        "del": "^4.1.0",
+        "express": "^4.16.4",
+        "html-entities": "^1.2.1",
         "http-proxy-middleware": "^0.19.1",
         "import-local": "^2.0.0",
-        "internal-ip": "^4.0.0",
+        "internal-ip": "^4.2.0",
         "ip": "^1.1.5",
-        "killable": "^1.0.0",
-        "loglevel": "^1.4.1",
-        "opn": "^5.1.0",
-        "portfinder": "^1.0.9",
+        "killable": "^1.0.1",
+        "loglevel": "^1.6.1",
+        "opn": "^5.5.0",
+        "portfinder": "^1.0.20",
         "schema-utils": "^1.0.0",
-        "selfsigned": "^1.9.1",
-        "semver": "^5.6.0",
-        "serve-index": "^1.7.2",
+        "selfsigned": "^1.10.4",
+        "semver": "^6.0.0",
+        "serve-index": "^1.9.1",
         "sockjs": "0.3.19",
         "sockjs-client": "1.3.0",
         "spdy": "^4.0.0",
-        "strip-ansi": "^3.0.0",
+        "strip-ansi": "^3.0.1",
         "supports-color": "^6.1.0",
         "url": "^0.11.0",
-        "webpack-dev-middleware": "^3.5.1",
+        "webpack-dev-middleware": "^3.6.2",
         "webpack-log": "^2.0.0",
-        "yargs": "12.0.2"
+        "yargs": "12.0.5"
       },
       "dependencies": {
         "ajv": {
-          "version": "6.9.2",
-          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz",
-          "integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==",
+          "version": "6.10.0",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+          "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
           "dev": true,
           "requires": {
             "fast-deep-equal": "^2.0.1",
@@ -13450,9 +13229,9 @@
           "dev": true
         },
         "camelcase": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-          "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+          "version": "5.3.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+          "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
           "dev": true
         },
         "cliui": {
@@ -13483,15 +13262,6 @@
             }
           }
         },
-        "decamelize": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
-          "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
-          "dev": true,
-          "requires": {
-            "xregexp": "4.0.0"
-          }
-        },
         "fast-deep-equal": {
           "version": "2.0.1",
           "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
@@ -13560,9 +13330,9 @@
           }
         },
         "p-limit": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
-          "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+          "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
           "dev": true,
           "requires": {
             "p-try": "^2.0.0"
@@ -13578,9 +13348,9 @@
           }
         },
         "p-try": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
-          "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
           "dev": true
         },
         "pkg-dir": {
@@ -13603,6 +13373,12 @@
             "ajv-keywords": "^3.1.0"
           }
         },
+        "semver": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz",
+          "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==",
+          "dev": true
+        },
         "strip-ansi": {
           "version": "3.0.1",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -13622,9 +13398,9 @@
           }
         },
         "webpack-dev-middleware": {
-          "version": "3.6.0",
-          "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.6.0.tgz",
-          "integrity": "sha512-oeXA3m+5gbYbDBGo4SvKpAHJJEGMoekUbHgo1RK7CP1sz7/WOSeu/dWJtSTk+rzDCLkPwQhGocgIq6lQqOyOwg==",
+          "version": "3.6.2",
+          "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.6.2.tgz",
+          "integrity": "sha512-A47I5SX60IkHrMmZUlB0ZKSWi29TZTcPz7cha1Z75yYOsgWh/1AcPmQEbC8ZIbU3A1ytSv1PMU0PyPz2Lmz2jg==",
           "dev": true,
           "requires": {
             "memory-fs": "^0.4.1",
@@ -13640,13 +13416,13 @@
           "dev": true
         },
         "yargs": {
-          "version": "12.0.2",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz",
-          "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==",
+          "version": "12.0.5",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+          "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
           "dev": true,
           "requires": {
             "cliui": "^4.0.0",
-            "decamelize": "^2.0.0",
+            "decamelize": "^1.2.0",
             "find-up": "^3.0.0",
             "get-caller-file": "^1.0.1",
             "os-locale": "^3.0.0",
@@ -13656,16 +13432,17 @@
             "string-width": "^2.0.0",
             "which-module": "^2.0.0",
             "y18n": "^3.2.1 || ^4.0.0",
-            "yargs-parser": "^10.1.0"
+            "yargs-parser": "^11.1.1"
           }
         },
         "yargs-parser": {
-          "version": "10.1.0",
-          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
-          "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
+          "version": "11.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+          "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
           "dev": true,
           "requires": {
-            "camelcase": "^4.1.0"
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
           }
         }
       }
@@ -13852,8 +13629,7 @@
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-      "dev": true
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
     },
     "write": {
       "version": "0.2.1",
@@ -13879,12 +13655,6 @@
       "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
       "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
     },
-    "xregexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
-      "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
-      "dev": true
-    },
     "xtend": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
@@ -14083,9 +13853,9 @@
           "dev": true
         },
         "camelcase": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
-          "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
+          "version": "5.3.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+          "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
           "dev": true
         },
         "cliui": {
@@ -14145,9 +13915,9 @@
           }
         },
         "p-limit": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
-          "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+          "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
           "dev": true,
           "requires": {
             "p-try": "^2.0.0"
@@ -14163,9 +13933,9 @@
           }
         },
         "p-try": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
-          "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
           "dev": true
         },
         "strip-ansi": {
@@ -14219,6 +13989,11 @@
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
       "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
+    },
+    "zone.js": {
+      "version": "0.8.29",
+      "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.29.tgz",
+      "integrity": "sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ=="
     }
   }
 }
diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json
index d24adfe..5d6e34a 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -26,11 +26,21 @@
     "win32"
   ],
   "dependencies": {
+    "@angular/common": "^7.2.2",
+    "@angular/compiler": "^7.2.2",
+    "@angular/core": "^7.2.2",
+    "@angular/forms": "^7.2.2",
+    "@angular/platform-browser": "^7.2.2",
+    "@angular/platform-browser-dynamic": "^7.2.2",
+    "@angular/router": "^7.2.2",
+    "@angular/upgrade": "^7.2.2",
     "@babel/plugin-transform-parameters": "7.0.0",
-    "@uirouter/angularjs": "1.0.20",
-    "@uirouter/core": "5.0.19",
+    "@uirouter/angular": "^3.0.0",
+    "@uirouter/angular-hybrid": "^7.0.0",
+    "@uirouter/angularjs": "^1.0.22",
+    "@uirouter/core": "^5.0.23",
     "@uirouter/rx": "0.5.0",
-    "angular": "1.7.6",
+    "angular": "^1.7.7",
     "angular-acl": "0.1.10",
     "angular-animate": "1.7.6",
     "angular-aria": "1.7.6",
@@ -51,6 +61,7 @@
     "bson-objectid": "1.1.5",
     "chart.js": "2.7.2",
     "chartjs-plugin-streaming": "1.6.1",
+    "core-js": "^2.6.0",
     "file-saver": "1.3.3",
     "font-awesome": "4.7.0",
     "jquery": "3.2.1",
@@ -59,19 +70,25 @@
     "jszip": "3.1.5",
     "lodash": "4.17.11",
     "natural-compare-lite": "1.4.0",
+    "ngx-popper": "^6.0.7",
     "nvd3": "1.8.6",
     "outdent": "0.5.0",
     "pako": "1.0.6",
+    "popper.js": "^1.14.7",
     "resize-observer-polyfill": "1.5.0",
     "roboto-font": "0.1.0",
     "rxjs": "6.3.3",
     "socket.io-client": "2.1.1",
-    "tf-metatags": "2.0.0"
+    "tf-metatags": "2.0.0",
+    "zone.js": "^0.8.29"
   },
   "devDependencies": {
+    "@angular/language-service": "^7.2.4",
     "@babel/core": "7.0.1",
     "@babel/plugin-proposal-class-properties": "7.0.0",
+    "@babel/plugin-proposal-decorators": "^7.2.2",
     "@babel/plugin-proposal-object-rest-spread": "7.0.0",
+    "@babel/plugin-syntax-decorators": "^7.2.0",
     "@babel/plugin-syntax-dynamic-import": "7.0.0",
     "@babel/preset-env": "7.0.0",
     "@babel/preset-typescript": "7.1.0",
diff --git a/modules/web-console/frontend/public/stylesheets/style.scss b/modules/web-console/frontend/public/stylesheets/style.scss
index 2e6a887..602853f 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -24,6 +24,12 @@
 :root {
     --sans-serif-font: Roboto;
     --serif-font: Roboto_slab;
+    --page-side-padding: 30px;
+    --form-gap: 10px;
+    --error-red-R: 238;
+    --error-red-G: 43;
+    --error-red-B: 39;
+    --error-red: rgb(var(--error-red-R), var(--error-red-G), var(--error-red-B));
 }
 
 body {
@@ -258,10 +264,6 @@ body {
     flex-direction: column;
 }
 
-:root {
-    --page-side-padding: 30px;
-}
-
 .wrapper {
     --header-height: 62px;
 
@@ -1308,14 +1310,6 @@ input[type="number"] {
     -moz-appearance: textfield;
 }
 
-input.ng-dirty.ng-invalid, button.ng-dirty.ng-invalid {
-    border-color: $ignite-invalid-color;
-
-    :focus {
-        border-color: $ignite-invalid-color;
-    }
-}
-
 .form-control-feedback {
     display: inline-block;
     color: $brand-primary;
diff --git a/modules/web-console/frontend/tsconfig.json b/modules/web-console/frontend/tsconfig.json
index 531a381..f06925c 100644
--- a/modules/web-console/frontend/tsconfig.json
+++ b/modules/web-console/frontend/tsconfig.json
@@ -7,7 +7,12 @@
         "noEmit": true,
         "allowJs": true,
         "checkJs": true,
-        "baseUrl": "."
+        "baseUrl": ".",
+        "experimentalDecorators": true,
+        "emitDecoratorMetadata": true,
+        "plugins": [
+            {"name": "@angular/language-service"}
+        ]
     },
     "exclude": [
         "build"
diff --git a/modules/web-console/frontend/webpack/webpack.common.js b/modules/web-console/frontend/webpack/webpack.common.js
index a93faf1..3cb8764 100644
--- a/modules/web-console/frontend/webpack/webpack.common.js
+++ b/modules/web-console/frontend/webpack/webpack.common.js
@@ -129,6 +129,13 @@ const config = {
             {
                 test: require.resolve('nvd3'),
                 use: 'expose-loader?nv'
+            },
+            {
+                // Mark files inside `@angular/core` as using SystemJS style dynamic imports.
+                // Removing this will cause deprecation warnings to appear.
+                // https://github.com/angular/angular/issues/21560#issuecomment-433601967
+                test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
+                parser: { system: true } // enable SystemJS
             }
         ]
     },