Non-RT RIC Dashboard 09/1309/2
authorPatrikBuhr <patrik.buhr@est.tech>
Tue, 29 Oct 2019 12:39:00 +0000 (13:39 +0100)
committerPatrikBuhr <patrik.buhr@est.tech>
Fri, 1 Nov 2019 12:51:31 +0000 (13:51 +0100)
First commit

Change-Id: I9e140d31d65d13df3ce07f6b87eac250ee952eab
Issue-ID: NONRTRIC-61
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
224 files changed:
dashboard/.gitignore [new file with mode: 0644]
dashboard/INFO.yaml [new file with mode: 0644]
dashboard/LICENSES.txt [new file with mode: 0644]
dashboard/README.md [new file with mode: 0644]
dashboard/a1-controller-client/.gitignore [new file with mode: 0644]
dashboard/a1-controller-client/README.md [new file with mode: 0644]
dashboard/a1-controller-client/pom.xml [new file with mode: 0644]
dashboard/a1-controller-client/src/main/resources/a1_controller_0.1.0.yaml [new file with mode: 0644]
dashboard/a1-controller-client/src/test/java/org/oransc/ric/portal/dashboard/a1controller/client/test/A1ControllerClientTest.java [new file with mode: 0644]
dashboard/docs/.gitignore [new file with mode: 0644]
dashboard/docs/_static/logo.png [new file with mode: 0644]
dashboard/docs/conf.py [new file with mode: 0644]
dashboard/docs/conf.yaml [new file with mode: 0644]
dashboard/docs/config-deploy.rst [new file with mode: 0644]
dashboard/docs/developer-guide.rst [new file with mode: 0644]
dashboard/docs/favicon.ico [new file with mode: 0644]
dashboard/docs/index.rst [new file with mode: 0644]
dashboard/docs/overview.rst [new file with mode: 0644]
dashboard/docs/release-notes.rst [new file with mode: 0644]
dashboard/docs/requirements-docs.txt [new file with mode: 0644]
dashboard/pom.xml [new file with mode: 0644]
dashboard/tox.ini [new file with mode: 0644]
dashboard/webapp-backend/.gitignore [new file with mode: 0644]
dashboard/webapp-backend/README.md [new file with mode: 0644]
dashboard/webapp-backend/config/.gitignore [new file with mode: 0644]
dashboard/webapp-backend/config/key.properties.template [new file with mode: 0644]
dashboard/webapp-backend/config/portal.properties.template [new file with mode: 0644]
dashboard/webapp-backend/pom.xml [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardUserManager.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1ControllerConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AdminConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/PortalApiConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SwaggerConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/A1Controller.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/Html5PathsController.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/SimpleErrorController.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/CaasIngressDemo.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/SimpleKubernetesClient.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/IDashboardResponse.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstance.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstances.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyType.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyTypes.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/util/HttpsURLConnectionUtils.java [new file with mode: 0644]
dashboard/webapp-backend/src/main/resources/ESAPI.properties [new file with mode: 0644]
dashboard/webapp-backend/src/main/resources/application.properties [new file with mode: 0644]
dashboard/webapp-backend/src/main/resources/logback.xml [new file with mode: 0644]
dashboard/webapp-backend/src/main/resources/static/error.html [new file with mode: 0644]
dashboard/webapp-backend/src/main/resources/validation.properties [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/DashboardTestServer.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1ControllerMockConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/PortalApIMockConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/anr-policy-instance.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/anr-policy-schema.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/caas-ingress-ricaux-pods.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/caas-ingress-ricplt-pods.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/demo-policy-schema-1.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/demo-policy-schema-2.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/demo-policy-schema-3.json [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/key.properties [new file with mode: 0644]
dashboard/webapp-backend/src/test/resources/portal.properties [new file with mode: 0644]
dashboard/webapp-frontend/.gitignore [new file with mode: 0644]
dashboard/webapp-frontend/README.md [new file with mode: 0644]
dashboard/webapp-frontend/angular.json [new file with mode: 0644]
dashboard/webapp-frontend/browserslist [new file with mode: 0644]
dashboard/webapp-frontend/e2e/protractor.conf.js [new file with mode: 0644]
dashboard/webapp-frontend/e2e/src/app.e2e-spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/e2e/src/app.po.ts [new file with mode: 0644]
dashboard/webapp-frontend/e2e/tsconfig.e2e.json [new file with mode: 0644]
dashboard/webapp-frontend/ng [new file with mode: 0755]
dashboard/webapp-frontend/npm [new file with mode: 0755]
dashboard/webapp-frontend/package-lock.json [new file with mode: 0644]
dashboard/webapp-frontend/package.json [new file with mode: 0644]
dashboard/webapp-frontend/pom.xml [new file with mode: 0644]
dashboard/webapp-frontend/proxy.conf.json [new file with mode: 0644]
dashboard/webapp-frontend/src/app/app-control/app-control.animations.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/app-control/app-control.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/app-control/app-control.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/app-control/app-control.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/app-control/app-control.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/app-control/app-control.datasource.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/control/control.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/control/control.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/control/control.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/control/control.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/footer/footer.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/footer/footer.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/footer/footer.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/footer/footer.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/interfaces/ac-xapp.types.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/interfaces/anr-xapp.types.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/interfaces/app-mgr.types.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/interfaces/dashboard.types.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/interfaces/e2-mgr.types.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/interfaces/policy.types.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/main/main.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/main/main.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/main/main.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/main/main.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-control.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-control.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-control.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-control.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/policy-control/policy-type.datasource.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-control.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-control.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-control.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-control.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ran-control/ran-control.datasource.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd-routing.module.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd.module.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/policy/policy.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/policy/policy.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/error-dialog.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/notification.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/notification.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/ui.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/ui/ui.service.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/user.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/user.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/user.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/user.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/user/user.datasource.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/ORANlogo.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/at_t.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/intelligence.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/latency.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/mockdata/config.json [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/mockdata/db.json [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/mockdata/routes.json [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/oran-logo.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/policy.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/profile_default.png [new file with mode: 0644]
dashboard/webapp-frontend/src/assets/xAppControl.png [new file with mode: 0644]
dashboard/webapp-frontend/src/environments/environment.prod.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/environments/environment.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/favicon.ico [new file with mode: 0644]
dashboard/webapp-frontend/src/index.html [new file with mode: 0644]
dashboard/webapp-frontend/src/karma.conf.js [new file with mode: 0644]
dashboard/webapp-frontend/src/main.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/polyfills.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/styles.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/styles/dark-theme.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/test.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/tsconfig.app.json [new file with mode: 0644]
dashboard/webapp-frontend/src/tsconfig.spec.json [new file with mode: 0644]
dashboard/webapp-frontend/src/tslint.json [new file with mode: 0644]
dashboard/webapp-frontend/tsconfig.json [new file with mode: 0644]
dashboard/webapp-frontend/tslint.json [new file with mode: 0644]

diff --git a/dashboard/.gitignore b/dashboard/.gitignore
new file mode 100644 (file)
index 0000000..675d063
--- /dev/null
@@ -0,0 +1,53 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+/logs
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node
+/node_modules
+
+/.classpath
+/.project
+/.settings
+/target/
+/.mvn/wrapper/maven-wrapper.jar
+/.tox
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+### visual studio ###
+.vs
+**/.vscode
+
+# OSx cruft
+**/.DS_Store
+
+# documentation
+.tox
+docs/_build/*
diff --git a/dashboard/INFO.yaml b/dashboard/INFO.yaml
new file mode 100644 (file)
index 0000000..2cc8827
--- /dev/null
@@ -0,0 +1,47 @@
+---
+project: 'portal/ric-dashboard'
+project_creation_date: '2019-05-21'
+project_category: ''
+lifecycle_state: 'Incubation'
+project_lead: &portal_ric_dashboard_ptl
+    name: 'TBD'
+    email: 'TBD'
+    id: 'TBD'
+    company: 'AT&T Labs-Research'
+    timezone: 'America/New_York'
+primary_contact: *portal_ric_dashboard_ptl
+issue_tracking:
+    type: 'jira'
+    url: 'https://jira.o-ran-sc.org/projects/RICPLT'
+    key: 'DASHBOARD'
+mailing_list:
+    type: 'groups.io'
+    url: 'https://lists.o-ran-sc.org/g/main'
+    tag: '<[portal/ric-dashboard]>'
+realtime_discussion:
+    type: 'irc'
+    server: 'freenode.net'
+    channel: '#o-ran-sc'
+meetings:
+    - type: 'zoom'
+      agenda: ''
+      url: ''
+      server: 'n/a'
+      channel: 'n/a'
+      repeats: 'weekly'
+      time: ''
+repositories:
+    - 'portal/ric-dashboard'
+committers:
+    - name: 'Chris Lott'
+      email: 'clott@research.att.com'
+      company: 'AT&T Labs-Research'
+      id: 'cl778h'
+      timezone: 'America/New_York'
+    - name: 'Manoop Talasila'
+      email: 'talasila@research.att.com'
+      company: 'ATT'
+      id: 'talasila'
+      timezone: 'America/New_York'
+tsc:
+    approval: 'https://lists.o-ran-sc.org/g/toc'
diff --git a/dashboard/LICENSES.txt b/dashboard/LICENSES.txt
new file mode 100644 (file)
index 0000000..4ec6357
--- /dev/null
@@ -0,0 +1,30 @@
+LICENSES.txt
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the "Software License");
+you may not use this software except in compliance with the Software
+License. You may obtain a copy of the Software License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Software License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the Software License for the specific language governing permissions
+and limitations under the Software License.
+
+
+
+Unless otherwise specified, all documentation contained herein is licensed
+under the Creative Commons License, Attribution 4.0 Intl. (the
+"Documentation License"); you may not use this documentation except in
+compliance with the Documentation License. You may obtain a copy of the
+Documentation License at
+
+https://creativecommons.org/licenses/by/4.0/
+
+Unless required by applicable law or agreed to in writing, documentation
+distributed under the Documentation License is distributed on an "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Documentation License for the specific language governing
+permissions and limitations under the Documentation License.
diff --git a/dashboard/README.md b/dashboard/README.md
new file mode 100644 (file)
index 0000000..9dbc046
--- /dev/null
@@ -0,0 +1,26 @@
+# O-RAN-SC RIC Dashboard Web Application
+
+The O-RAN SC RIC Dashboard provides administrative and operator
+functions for a radio access network (RAN) controller.
+This web app consists of an Angular (version 8) front end
+and a Java (version 11) Spring-Boot (version 2.1) back end.
+
+Please see the documentation in the docs/ folder.
+
+The backend server publishes live API documentation at the
+URL `http://your-host-name-here:8080/swagger-ui.html`
+
+## License
+
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/dashboard/a1-controller-client/.gitignore b/dashboard/a1-controller-client/.gitignore
new file mode 100644 (file)
index 0000000..27fd461
--- /dev/null
@@ -0,0 +1,23 @@
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# exclude jar for gradle wrapper
+!gradle/wrapper/*.jar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# build files
+**/target
+target
+.gradle
+build
+
+logs/
diff --git a/dashboard/a1-controller-client/README.md b/dashboard/a1-controller-client/README.md
new file mode 100644 (file)
index 0000000..b93a390
--- /dev/null
@@ -0,0 +1,29 @@
+# A1 Controller Client Generator
+
+This projects generates a REST client library from the OpenAPI specification
+file stored here and packages it in a jar.
+
+## Eclipse and STS Users
+
+The Swagger Codegen maven plugin is not supported in Eclipse/STS. You can
+limp along by taking these steps:
+
+1. Generate the code using maven:
+    mvn install
+2. Add this folder to the project build path:
+    target/generated-sources/swagger/src/main/java
+
+## License
+
+Copyright (C) 2019 Nordix Foundation
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/dashboard/a1-controller-client/pom.xml b/dashboard/a1-controller-client/pom.xml
new file mode 100644 (file)
index 0000000..cfd67e2
--- /dev/null
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--<![CDATA[
+========================LICENSE_START=================================
+O-RAN-SC
+%%
+Copyright (C) 2019 Nordix Foundation
+%%
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+========================LICENSE_END===================================
+]]>-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.o-ran-sc.portal.ric-dashboard</groupId>
+               <artifactId>ric-dash-parent</artifactId>
+               <version>1.2.5-SNAPSHOT</version>
+       </parent>
+       <!-- This groupId will NOT allow deployment in LF -->
+       <groupId>org.o-ran-sc.ric.plt.a1controller.client</groupId>
+       <artifactId>a1-controller-client</artifactId>
+       <name>RIC A1 Controller client</name>
+       <version>0.1.0-SNAPSHOT</version>
+       <properties>
+               <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+               <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+               <!-- Jenkins invokes maven with -Dbuild.number=.. -->
+               <build.number>0</build.number>
+               <!-- same as groupId BUT without hyphens -->
+               <client.base.package.name>org.oransc.ric.a1controller.client</client.base.package.name>
+       </properties>
+       <!-- Successful compilation requires generated code dependencies -->
+       <dependencies>
+               <!-- Required for Java 9 and later -->
+               <dependency>
+                       <groupId>javax.annotation</groupId>
+                       <artifactId>javax.annotation-api</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>io.swagger.core.v3</groupId>
+                       <artifactId>swagger-annotations</artifactId>
+                       <version>2.0.8</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-context</artifactId>
+               </dependency>
+               <!-- HTTP client: Spring RestTemplate -->
+               <dependency>
+                       <groupId>org.springframework</groupId>
+                       <artifactId>spring-web</artifactId>
+               </dependency>
+               <!-- JSON processing: jackson -->
+               <dependency>
+                       <groupId>com.fasterxml.jackson.core</groupId>
+                       <artifactId>jackson-core</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>com.fasterxml.jackson.core</groupId>
+                       <artifactId>jackson-annotations</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>com.fasterxml.jackson.core</groupId>
+                       <artifactId>jackson-databind</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                       <artifactId>jackson-jaxrs-json-provider</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>com.fasterxml.jackson.datatype</groupId>
+                       <artifactId>jackson-datatype-jsr310</artifactId>
+               </dependency>
+               <!-- test dependencies -->
+               <dependency>
+                       <groupId>org.junit.jupiter</groupId>
+                       <artifactId>junit-jupiter-api</artifactId>
+                       <scope>test</scope>
+               </dependency>
+                <dependency>
+                        <groupId>org.slf4j</groupId>
+                        <artifactId>slf4j-api</artifactId>
+                </dependency>
+       </dependencies>
+       <build>
+               <plugins>
+                       <plugin>
+                               <!-- This 2019 version is required for OpenAPI 3 -->
+                               <groupId>io.swagger.codegen.v3</groupId>
+                               <artifactId>swagger-codegen-maven-plugin</artifactId>
+                               <version>3.0.8</version>
+                               <executions>
+                                       <execution>
+                                               <goals>
+                                                       <goal>generate</goal>
+                                               </goals>
+                                               <configuration>
+                                                       <inputSpec>${project.basedir}/src/main/resources/a1_controller_0.1.0.yaml</inputSpec>
+                                                       <language>java</language>
+                                                       <packageName>${client.base.package.name}</packageName>
+                                                       <modelPackage>${client.base.package.name}.model</modelPackage>
+                                                       <apiPackage>${client.base.package.name}.api</apiPackage>
+                                                       <invokerPackage>${client.base.package.name}.invoker</invokerPackage>
+                                                       <configOptions>
+                                                               <groupId>${project.groupId}</groupId>
+                                                               <artifactId>${project.artifactId}</artifactId>
+                                                               <artifactVersion>${project.version}</artifactVersion>
+                                                               <library>resttemplate</library>
+                                                               <java8>true</java8>
+                                                               <dateLibrary>java8</dateLibrary>
+                                                               <licenseName>Apache 2.0</licenseName>
+                                                               <licenseUrl>https://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
+                                                       </configOptions>
+                                               </configuration>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <!-- add build information to manifest. Java provides access to the implementation 
+                               version for a package, so cram the build number into there. -->
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-jar-plugin</artifactId>
+                               <configuration>
+                                       <archive>
+                                               <manifest>
+                                                       <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                                               </manifest>
+                                               <manifestEntries>
+                                                       <Implementation-Version>${project.version}-b${build.number}</Implementation-Version>
+                                               </manifestEntries>
+                                       </archive>
+                               </configuration>
+                       </plugin>
+               </plugins>
+               <pluginManagement>
+                       <plugins>
+                               <!--This plugin's configuration is used to store Eclipse m2e settings 
+                                       only. It has no influence on the Maven build itself. -->
+                               <plugin>
+                                       <groupId>org.eclipse.m2e</groupId>
+                                       <artifactId>lifecycle-mapping</artifactId>
+                                       <version>1.0.0</version>
+                                       <configuration>
+                                               <lifecycleMappingMetadata>
+                                                       <pluginExecutions>
+                                                               <pluginExecution>
+                                                                       <pluginExecutionFilter>
+                                                                               <groupId>io.swagger.codegen.v3</groupId>
+                                                                               <artifactId>swagger-codegen-maven-plugin</artifactId>
+                                                                               <versionRange>[1.0,)</versionRange>
+                                                                               <goals>
+                                                                                       <goal>generate</goal>
+                                                                               </goals>
+                                                                       </pluginExecutionFilter>
+                                                                       <action>
+                                                                               <ignore />
+                                                                       </action>
+                                                               </pluginExecution>
+                                                       </pluginExecutions>
+                                               </lifecycleMappingMetadata>
+                                       </configuration>
+                               </plugin>
+                       </plugins>
+               </pluginManagement>
+       </build>
+</project>
diff --git a/dashboard/a1-controller-client/src/main/resources/a1_controller_0.1.0.yaml b/dashboard/a1-controller-client/src/main/resources/a1_controller_0.1.0.yaml
new file mode 100644 (file)
index 0000000..8d74a41
--- /dev/null
@@ -0,0 +1,558 @@
+# ==================================================================================
+#       Copyright (c) 2019 Nokia
+#       Copyright (c) 2018-2019 AT&T Intellectual Property.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#          http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+# ==================================================================================
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: RIC A1
+paths:
+  '/A1-ADAPTER-API:getNearRT-RICs':
+    post:
+      description: >
+        Get a list of all nearRT-RICs
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_all_nearrt_rics
+      responses:
+        '200':
+          description: >
+            Successfully got the list of all nearRT-RICs.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_NRRids_list_schema"
+                
+  '/A1-ADAPTER-API:getHealthCheck':
+    post:
+      description: >
+        Get health status for a Near-RT-RIC. true - health ok, false - health is not ok.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_healthcheck
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_schema"
+      responses:
+        '200':
+          description: >
+            Successfully got the health status.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_healthstatus_schema"
+            
+  '/A1-ADAPTER-API:getPolicyTypes':
+    post:
+      description: >
+        Get a list of all registered policy-type-ids.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_all_policy_types
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_schema"
+      responses:
+        '200':
+          description: >
+            Successfully got the list of all policy-type-ids.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_PTids_list_schema"
+            
+  '/A1-ADAPTER-API:createPolicyType':
+    post:
+      description: >
+        Create a policy type.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.create_policy_type
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_desc_name_PT_schema"
+      responses:
+        '201':
+          description: >
+            Successfully created the policy type.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_status_code_schema"
+        '400':
+          description: >
+            illegal policy_type_id, or this policy type already exists
+            
+  '/A1-ADAPTER-API:getPolicyType':
+    post:
+      description: >
+        Get a policy type.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_policy_type
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_schema"
+      responses:
+        '200':
+          description: >
+            Successfully got the policy type.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_desc_name_PT_schema"
+        '404':
+          description: >
+            there is no policy type with this policy_type_id
+            
+  '/A1-ADAPTER-API:deletePolicyType':
+    post:
+      description: >
+        Delete a policy type.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.delete_policy_type
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_schema"
+      responses:
+        '204':
+          description: >
+            Successfully deleted the policy type.
+        '400':
+          description: >
+            Policy type cannot be deleted because there are instances. All instances must be removed before a policy type can be deleted.
+        '404':
+          description: >
+            there is no policy type with this policy_type_id
+            
+  '/A1-ADAPTER-API:getPolicyInstances':
+    post:
+      description: >
+        Get a list of all policy-instance-ids for this policy-type-id.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_all_instances_for_type
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_schema"
+      responses:
+        '200':
+          description: >
+            Successfully got the list of all policy-instance-ids for this policy-type-id.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_PIids_list_schema"
+        '404':
+          description: >
+            there is no policy type with this policy_type_id
+            
+  '/A1-ADAPTER-API:createPolicyInstance':
+    post:
+      description: >
+        Create a policy instance.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.create_policy_instance
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_PIid_PI_schema"
+      responses:
+        '201':
+          description: >
+            Successfully created the policy instance.
+        '400':
+          description: >
+            Bad input data for this policy instance.
+        '404':
+          description: >
+            there is no policy type with this policy_type_id
+            
+  '/A1-ADAPTER-API:getPolicyInstance':
+    post:
+      description: >
+        Get a policy instance.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_policy_instance
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_PIid_schema"
+      responses:
+        '200':
+          description: >
+            Successfully got the policy instance.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_PI_schema"
+        '404':
+          description: >
+            there is no policy instance with this policy_instance_id or there is no policy type with this policy_type_id
+
+  '/A1-ADAPTER-API:deletePolicyInstance':
+    post:
+      description: >
+        Delete a policy instance.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.delete_policy_instance
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_PIid_schema"
+      responses:
+        '204':
+          description: >
+            Successfully deleted the policy instance.
+        '404':
+          description: >
+            there is no policy instance with this policy_instance_id or there is no policy type with this policy_type_id
+            
+  '/A1-ADAPTER-API:getStatus':
+    post:
+      description: >
+        Get the status for a policy instance.
+      tags:
+        - A1 Controller
+      operationId: a1.controller.get_policy_instance_status
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              "$ref": "#/components/schemas/input_NRRid_PTid_PIid_schema"
+      responses:
+        '200':
+          description: >
+            Successfully got the policy instance status.
+          content:
+            application/json:
+              schema:
+                "$ref": "#/components/schemas/output_status_schema"
+        '404':
+          description: >
+            there is no policy instance with this policy_instance_id or there is no policy type with this policy_type_id
+
+components:    
+  schemas:
+    input_NRRid_schema:
+      type: object
+      required:
+      - input
+      additionalProperties: false
+      properties:
+        input:
+          type: object
+          required:
+          - near-rt-ric-id
+          additionalProperties: false
+          properties:
+            near-rt-ric-id:
+              "$ref": "#/components/schemas/near_rt_ric_id"
+          
+    input_NRRid_PTid_schema:
+      type: object
+      required:
+      - input
+      additionalProperties: false
+      properties:
+        input:
+          type: object
+          required:
+          - near-rt-ric-id
+          - policy-type-id
+          additionalProperties: false
+          properties:
+            near-rt-ric-id:
+              "$ref": "#/components/schemas/near_rt_ric_id"
+            policy-type-id:
+              "$ref": "#/components/schemas/policy_type_id"
+          
+    input_NRRid_PTid_PIid_schema:
+      type: object
+      required:
+      - input
+      additionalProperties: false
+      properties:
+        input:
+          type: object
+          required:
+          - near-rt-ric-id
+          - policy-type-id
+          - policy-instance-id
+          additionalProperties: false
+          properties:
+            near-rt-ric-id:
+              "$ref": "#/components/schemas/near_rt_ric_id"
+            policy-type-id:
+              "$ref": "#/components/schemas/policy_type_id"
+            policy-instance-id:
+              "$ref": "#/components/schemas/policy_instance_id"
+          
+    input_NRRid_PTid_PIid_PI_schema:
+      type: object
+      required:
+      - input
+      additionalProperties: false
+      properties:
+        input:
+          type: object
+          required:
+          - near-rt-ric-id
+          - policy-type-id
+          - policy-instance-id
+          - policy-instance
+          additionalProperties: false
+          properties:
+            near-rt-ric-id:
+              "$ref": "#/components/schemas/near_rt_ric_id"
+            policy-type-id:
+              "$ref": "#/components/schemas/policy_type_id"
+            policy-instance-id:
+              "$ref": "#/components/schemas/policy_instance_id"
+            policy-instance:
+              "$ref": "#/components/schemas/policy_instance"
+              
+    input_NRRid_PTid_desc_name_PT_schema:
+      type: object
+      required:
+      - input
+      additionalProperties: false
+      properties:
+        input:
+          type: object
+          required:
+          - near-rt-ric-id
+          - policy-type-id
+          - description
+          - name
+          - policy-type
+          additionalProperties: false
+          properties:
+            near-rt-ric-id:
+              "$ref": "#/components/schemas/near_rt_ric_id"
+            policy-type-id:
+              "$ref": "#/components/schemas/policy_type_id"
+            description:
+              type: string
+            name:
+              type: string
+            policy-type:
+              "$ref": "#/components/schemas/policy_type"
+             
+    output_NRRids_list_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - near-rt-ric-id-list
+          additionalProperties: false
+          properties:
+            near-rt-ric-id-list:
+              type: array
+              items:
+                "$ref": "#/components/schemas/near_rt_ric_id"
+                
+    output_healthstatus_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - health-status
+          additionalProperties: false
+          properties:
+            health-status:
+              type: boolean
+          
+    output_desc_name_PT_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - description
+          - name
+          - policy_type
+          additionalProperties: false
+          properties:
+            description:
+              type: string
+            name:
+              type: string
+            policy-type:
+              "$ref": "#/components/schemas/policy_type"
+              
+    output_PTids_list_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - policy-type-id-list
+          additionalProperties: false
+          properties:
+            policy-type-id-list:
+              type: array
+              items:
+                "$ref": "#/components/schemas/policy_type_id"
+                
+    output_PIids_list_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - policy-instance-id-list
+          additionalProperties: false
+          properties:
+            policy-instance-id-list:
+              type: array
+              items:
+                "$ref": "#/components/schemas/policy_instance_id"
+              
+    output_PI_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - policy-instance
+          additionalProperties: false
+          properties:
+            policy-instance:
+              "$ref": "#/components/schemas/policy_instance"
+              
+    output_status_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - status
+          additionalProperties: false
+          properties:
+            status:
+              type: string
+              
+    output_status_code_schema:
+      type: object
+      required:
+      - output
+      additionalProperties: false
+      properties:
+        output:
+          type: object
+          required:
+          - status
+          - code
+          additionalProperties: false
+          properties:
+            status:
+              type: string
+            code:
+              type: string
+          
+    near_rt_ric_id:
+      description: >
+        represents a near RT RIC identifier. Currently this can be any string.
+      type: string
+      example: near-rt-ric-1
+      
+    policy_type_id:
+      description: >
+        represents a policy type identifier. Currently this is restricted to an integer range.
+      type: integer
+      minimum: 20000
+      maximum: 21023
+      example: 20000
+
+    policy_instance_id:
+      description: >
+        represents a policy instance identifier. UUIDs are advisable but can be any string
+      type: string
+      example: 3d2157af-6a8f-4a7c-810f-38c2f824bf12
+      
+    policy_type:
+      description: >
+        represents a policy type. String is used for now to represent this
+      type: string
+      example: 
+        "{type: A}"
+        
+    policy_instance:
+      description: >
+        represents a policy instance. String is used for now to represent this
+      type: string
+      example: 
+        "{slice_id: slice-1, priority_level: high}"
+      
+  securitySchemes:
+    basicAuth:
+      type: http
+      scheme: basic
+      
+security:
+  - basicAuth: []
\ No newline at end of file
diff --git a/dashboard/a1-controller-client/src/test/java/org/oransc/ric/portal/dashboard/a1controller/client/test/A1ControllerClientTest.java b/dashboard/a1-controller-client/src/test/java/org/oransc/ric/portal/dashboard/a1controller/client/test/A1ControllerClientTest.java
new file mode 100644 (file)
index 0000000..cbafba4
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.a1controller.client.test;
+
+import java.lang.invoke.MethodHandles;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.invoker.ApiClient;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.client.RestClientException;
+
+/**
+ * Demonstrates use of the generated A1 controller client.
+ *
+ * The tests fail because no server is available.
+ */
+public class A1ControllerClientTest {
+
+    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       @Test
+       public void demo() {
+               ApiClient apiClient = new ApiClient();
+               apiClient.setBasePath("http://localhost:30099/");
+               A1ControllerApi a1Api = new A1ControllerApi(apiClient);
+               try {
+                       Object o = a1Api.a1ControllerGetPolicyInstance(new InputNRRidPTidPIidSchema());
+                       logger.info(
+                                       "getPolicy answered code {}, content {} ", apiClient.getStatusCode().toString(), o.toString());
+                       Assertions.assertTrue(apiClient.getStatusCode().is2xxSuccessful());
+               } catch (RestClientException e) {
+                       logger.error("getPolicy failed: {}", e.toString());
+               }
+               try {
+                       String policy = "{}";
+                       InputNRRidPTidPIidPISchema body = new InputNRRidPTidPIidPISchema();
+                       InputNRRidPTidPIidPISchemaInput input = new InputNRRidPTidPIidPISchemaInput();
+                       input.setNearRtRicId("1");
+                       input.setPolicyTypeId(1);
+                       input.setPolicyInstanceId("1");
+                       input.setPolicyInstance("{}");
+                       body.setInput(input);
+                       a1Api.a1ControllerCreatePolicyInstance(body);
+                       logger.info("putPolicy answered: {}", apiClient.getStatusCode().toString());
+                       Assertions.assertTrue(apiClient.getStatusCode().is2xxSuccessful());
+               } catch (RestClientException e) {
+                       logger.error("getPolicy failed: {}", e.toString());
+               }
+       }
+}
diff --git a/dashboard/docs/.gitignore b/dashboard/docs/.gitignore
new file mode 100644 (file)
index 0000000..173f8dc
--- /dev/null
@@ -0,0 +1,3 @@
+/_build
+/out
+/_build
diff --git a/dashboard/docs/_static/logo.png b/dashboard/docs/_static/logo.png
new file mode 100644 (file)
index 0000000..c3b6ce5
Binary files /dev/null and b/dashboard/docs/_static/logo.png differ
diff --git a/dashboard/docs/conf.py b/dashboard/docs/conf.py
new file mode 100644 (file)
index 0000000..922e22f
--- /dev/null
@@ -0,0 +1,6 @@
+from docs_conf.conf import *
+linkcheck_ignore = [
+    'http://localhost.*',
+    'http://127.0.0.1.*',
+    'https://gerrit.o-ran-sc.org.*'
+]
diff --git a/dashboard/docs/conf.yaml b/dashboard/docs/conf.yaml
new file mode 100644 (file)
index 0000000..127745e
--- /dev/null
@@ -0,0 +1,3 @@
+---
+project_cfg: oran
+project: portal-ric-dashboard
diff --git a/dashboard/docs/config-deploy.rst b/dashboard/docs/config-deploy.rst
new file mode 100644 (file)
index 0000000..6a843ca
--- /dev/null
@@ -0,0 +1,267 @@
+.. ===============LICENSE_START=======================================================
+.. O-RAN SC CC-BY-4.0
+.. %%
+.. Copyright (C) 2019 AT&T Intellectual Property
+.. %%
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..      http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+.. ===============LICENSE_END=========================================================
+
+RIC Dashboard Configuration and Deployment
+==========================================
+
+This documents the configuration and deployment of the O-RAN SC RIC
+Dashboard web application, which is often deployed together with the
+ONAP Portal.
+
+Configuration
+-------------
+
+The application requires the following configuration files::
+
+    application.properties
+    key.properties
+    portal.properties
+
+In the usual Kubernetes deployment, all file contents are provided by
+a configuration map.
+
+Application Properties
+^^^^^^^^^^^^^^^^^^^^^^
+
+The file ``application.properties`` must be provided when the
+application is launched, either in the current working directory or in
+a ``config`` subdirectory (latter is preferred). The Helm chart that
+deploys the application should mount this file appropriately.
+
+Many properties have default values cached within the application, in
+file ``src/main/resources/application.properties``.  Properties with
+default values do NOT need to be repeated in a deployment-specific
+configuration.  Properties without default values MUST be specified in
+a deployment-specific configuration.
+
+The properties are listed below in alphabetical order.
+
+``a1med.url.prefix``
+
+A1 Mediator URL prefix.  No useful default. Usually a service name
+like ``http://ricplt-entry/a1mediator``
+
+``a1med.url.suffix``
+
+A1 Mediator URL suffix. Default is the empty string.
+
+``anrxapp.url.prefix``
+
+ANR Application URL prefix.  No useful default. Usually a service name
+like ``http://ricxapp-entry/anr``
+
+``anrxapp.url.suffix``
+
+ANR Application URL suffix. Default is the empty string.
+
+``appmgr.url.prefix``
+
+Application Manager URL prefix. No useful default. Usually a service
+name like ``http://ricplt-entry/appmgr``
+
+``appmgr.url.suffix``
+
+Application Manager URL suffix. Default is ``/ric/v1``.
+
+``caasingress.aux.url.prefix``
+
+CAAS-Ingress application URL prefix for the RIC Auxiliary cluster.  No useful default.
+
+``caasingress.aux.url.suffix``
+
+CAAS-Ingress application URL suffix for the RIC Auxiliary cluster. Default is ``api``.
+
+``caasingress.insecure``
+
+Flag whether to disable SSL/TLS certificate and hostname verification.
+If true, the dashboard can communicate with a CAAS-Ingress endpoint that
+uses self-signed certificates.
+
+``caasingress.plt.url.prefix``
+
+CAAS-Ingress application URL prefix for the RIC Platform cluster.  No useful default.
+
+``caasingress.plt.url.suffix``
+
+CAAS-Ingress application URL suffix for the RIC-PLT cluster. Default is ``api``.
+
+``e2mgr.url.prefix``
+
+E2 Manager URL prefix. No useful default. Usually a service name like
+``http://ricplt-entry/e2mgr``
+
+``e2mgr.url.suffix``
+
+E2 Manager URL prefix. Default is ``/v1``.
+
+``mock.config.delay``
+
+Sleep period for mock methods in milliseconds.  This mimics slow
+endpoints. Default is ``0``.
+
+``portalapi.appname``
+
+Application name expected at ONAP portal. Default is ``RIC Dashboard``
+
+``portalapi.decryptor``
+
+Java class that decrypts ciphertext from Portal. Default is
+``org.oransc.ric.portal.dashboard.portalapi.PortalSdkDecryptorAes``.
+
+``portalapi.password``
+
+REST password expected at ONAP portal. No default value.
+
+``portalapi.security``
+
+Boolean flag whether the Dashboard limits access to users (browsers)
+that present security tokens set by the ONAP Portal.  If false, no
+access control is performed, which is only appropriate for isolated
+lab testing.
+
+``portalapi.usercookie``
+
+Name of request cookie with user ID. Default is ``UserId``.
+
+``portalapi.username``
+
+REST user name expected at ONAP portal. No default value.
+
+``server.port``
+
+Port where the Tomcat server listens for requests. Default is ``8080``.
+
+``metrics.url.ac``
+
+Url to the kibana source which visualizes AC App metrics. No default value and needs to be replaced with actual value during deployment time.
+
+``userfile``
+
+Path of file that stores user details. Default is ``users.json``.
+
+
+Key Properties
+^^^^^^^^^^^^^^
+
+The file ``key.properties`` must be provided on the Java classpath for
+the Spring-Boot application, as required by the EPSDK-FW library. The
+Helm chart for the application should mount this file appropriately.
+A sample file is in directory ``src/test/resources``.
+
+The file must contain the following entries, listed here in
+alphabetical order.
+
+``cipher.enc.key``
+
+Encryption key used by the EPSDK-FW library.  No default value.
+
+
+Portal Properties
+^^^^^^^^^^^^^^^^^
+
+The file ``portal.properties`` must be provided on the Java classpath
+for the application, as required by the EPSDK-FW library.  The Helm
+chart for the application should mount this file appropriately.  A
+sample file is in directory ``src/test/resources``.
+
+The file must contain the following entries, listed here in
+alphabetical order.
+
+``ecomp_redirect_url``
+
+Portal URL that is reachable by a user's browser.  This is a value
+like
+``https://portal.api.simpledemo.onap.org:30225/ONAPPORTAL/login.htm``
+
+``ecomp_rest_url``
+
+Portal REST URL that is reachable by the Dashboard back-end. 
+This is a value like ``http://portal-app.onap:8989/ONAPPORTAL/auxapi``
+
+``portal.api.impl.class``
+
+Java class name.  No default value.  Value must be
+``org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl``
+
+``role_access_centralized``
+
+Selector for role access.  No default value.  Value must be ``remote``.
+
+``ueb_app_key``
+
+Unique key assigned by ONAP Portal to the RIC Dashboard application.
+No default value.
+
+
+Deployment
+----------
+
+A production server requires the configuration files listed above.
+All files should be placed in a ``config`` directory.  That name is
+important; Spring automatically searches that directory for the
+``application.properties`` file. Further, that directory can easily be
+placed on the Java classpath so the additional files can be found at
+runtime.
+
+
+On-Board Dashboard to ONAP Portal
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When on-boarding the Dashboard to the ONAP Portal the administrator
+must supply the following information about the deployed instance:
+
+- Dashboard URL that is reachable by a user's browser. The domain of
+  this host name must match the Portal URL that is similarly reachable
+  by a user's browser for cookie-based authentication to function as
+  expected.  This should be a value like
+  ``http://dashboard.simpledemo.onap.org:8080``
+- Dashboard REST URL that is reachable by the Portal back-end server.
+  This can be a host name or an IP address, because it does not use
+  cookie-based authentication.  This must be a URL with suffix "/api/v3"
+  for example ``http://192.168.1.1:8080/api/v3``.
+
+The Dashboard server only listens on a single port, so the examples
+above both use the same port number.  Different port numbers might be
+required if an ingress controller or other proxy server is used.
+
+After the on-boarding process is complete, the administrator must
+enter values from the Portal for the following properties explained
+above:
+
+- ``portalapi.password``
+- ``portalapi.username``
+- ``ueb_app_key``
+
+Launch Server
+^^^^^^^^^^^^^
+
+After creating, populating and mounting Kubernetes config maps
+appropriately, launch the server with this command-line invocation to
+include the ``config`` directory on the Java classpath::
+
+    java -cp config:target/ric-dash-be-1.2.0-SNAPSHOT.jar \
+        -Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication \
+        org.springframework.boot.loader.PropertiesLauncher
+
+Alternately, to use the configuration in the "application-abc.properties" file,
+modify the command to have "spring.config.name=name" like this::
+
+    java -cp config:target/ric-dash-be-1.2.0-SNAPSHOT.jar \
+        -Dspring.config.name=application-abc \
+        -Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication \
+        org.springframework.boot.loader.PropertiesLauncher
diff --git a/dashboard/docs/developer-guide.rst b/dashboard/docs/developer-guide.rst
new file mode 100644 (file)
index 0000000..f99a04d
--- /dev/null
@@ -0,0 +1,148 @@
+.. ===============LICENSE_START=======================================================
+.. O-RAN SC CC-BY-4.0
+.. %%
+.. Copyright (C) 2019 AT&T Intellectual Property
+.. %%
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..      http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+.. ===============LICENSE_END=========================================================
+
+RIC Dashboard Developer Guide
+=============================
+
+This document provides a quickstart for developers of the O-RAN SC RIC Dashboard web
+application.
+
+Prerequisites
+-------------
+
+1. Java development kit (JDK), version 11 or later
+2. Maven dependency-management tool, version 3.4 or later
+
+Other required tools including the Node Package Manager (npm) are fetched dynamically.
+
+Clone and Update Submodules
+---------------------------
+
+After cloning the repository, initialize and update all git submodules like this::
+
+    git submodule init
+    git submodule update
+    
+Check the submodule status at any time like this::
+
+    git submodule status
+
+
+Angular Front-End Application
+-----------------------------
+
+The Angular 8 application files are in subdirectory ``webapp-frontend``.
+Build the front-end application via ``mvn package``.  For development and debugging,
+build the application, then launch an ng development server using this command::
+
+    ./ng serve --proxy-config proxy.conf.json
+
+The app will automatically reload in the browser if you change any of the source files.
+The ng server listens for requests at this URL:
+
+    http://localhost:4200
+
+
+Spring-Boot Back-End Application
+--------------------------------
+
+A development (not production) server uses local configuration and serves mock data
+that simulates the behavior of remote endpoints.  The back-end server listens for
+requests at this URL:
+
+    http://localhost:8080
+
+The directory ``src/test/resources`` contains usable versions of the required property
+files.  These steps are required to launch:
+
+1. Set an environment variable via JVM argument: ``-Dorg.oransc.ric.portal.dashboard=mock``
+2. Run the JUnit test case ``DashboardServerTest`` which is not exactly a "test" because it never finishes.
+
+Both steps can be done with this command-line invocation::
+
+     mvn -Dorg.oransc.ric.portal.dashboard=mock -Dtest=DashboardTestServer test
+
+Development user authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The development server requires basic HTTP user authentication for all requests. Like
+the production server, it requires HTTP headers with authentication for Portal API
+requests.  The username and password are stored in constants in this Java class in
+the ``src/test/java`` folder::
+
+    org.oransc.ric.portal.dashboard.config.WebSecurityMockConfiguration
+
+Like the production server, the development server also performs role-based
+authentication on requests. The user name-role name associations are also defined
+in the class shown above.
+
+Production user authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The server uses the ONAP Portal's "EPSDK-FW" library to support a
+single-sign-on (SSO) feature, which requires users to authenticate
+at the ONAP Portal UI. The RIC Dashboard can be on-boarded as an 
+application on the ONAP Portal using its application on-boarding UI.
+
+The server authenticates requests using cookies that are set
+by the ONAP Portal::
+
+     EPService
+     UserId
+
+The EPService value is not checked.  The UserId value is decrypted
+using a secret key shared with the ONAP Portal to yield a user ID.
+That ID must match a user's loginId defined in the user manager.
+
+The regular server checks requests for the following granted
+authorities (role names), as defined in the java class ``DashboardConstants``.
+A standard user can invoke all "GET" methods but not make changes.
+A system administrator can invoke all methods ("GET", "POST", "PUT",
+"DELETE") to make arbitrary changes::
+
+    Standard_User
+    System_Administrator
+
+Use the following structure in a JSON file to publish a user for the
+user manager::
+
+    [
+     {
+      "orgId":null,
+      "managerId":null,
+      "firstName":"Demo",
+      "middleInitial":null,
+      "lastName":"User",
+      "phone":null,
+      "email":null,
+      "hrid":null,
+      "orgUserId":null,
+      "orgCode":null,
+      "orgManagerUserId":null,
+      "jobTitle":null,
+      "loginId":"demo",
+      "active":true,
+      "roles":[
+         {
+            "id":null,
+            "name":"Standard_User",
+            "roleFunctions":null
+         }
+      ]
+     }
+    ]
diff --git a/dashboard/docs/favicon.ico b/dashboard/docs/favicon.ico
new file mode 100644 (file)
index 0000000..00b0fd0
Binary files /dev/null and b/dashboard/docs/favicon.ico differ
diff --git a/dashboard/docs/index.rst b/dashboard/docs/index.rst
new file mode 100644 (file)
index 0000000..9c40271
--- /dev/null
@@ -0,0 +1,36 @@
+.. _portal-ric-dashboard:
+
+.. ===============LICENSE_START======================================================
+.. O-RAN SC CC-BY-4.0
+.. %%
+.. Copyright (C) 2019 AT&T Intellectual Property
+.. %%
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..      http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+.. ===============LICENSE_END========================================================
+
+.. RIC Dashboard documentation master
+
+RIC Dashboard
+=============
+
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   overview.rst
+   config-deploy.rst
+   developer-guide.rst
+   release-notes.rst
+
+* :ref:`search`
diff --git a/dashboard/docs/overview.rst b/dashboard/docs/overview.rst
new file mode 100644 (file)
index 0000000..a58986e
--- /dev/null
@@ -0,0 +1,98 @@
+.. ===============LICENSE_START=======================================================
+.. O-RAN SC CC-BY-4.0
+.. %%
+.. Copyright (C) 2019 AT&T Intellectual Property
+.. %%
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..      http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+.. ===============LICENSE_END=========================================================
+
+RIC Dashboard Overview
+======================
+
+The O-RAN SC RIC Dashboard provides administrative and operator
+functions for a radio access network (RAN) controller.  The web app is
+built as a single-page app using an Angular (version 8) front end and
+a Java (version 11) Spring-Boot (version 2.1) back end.
+
+Project Resources
+-----------------
+
+The source code is available from the Linux Foundation Gerrit server:
+
+    `<https://gerrit.o-ran-sc.org/r/portal/ric-dashboard;a=summary>`_
+
+The build (CI) jobs are in the Linux Foundation Jenkins server:
+
+    `<https://jenkins.o-ran-sc.org/view/portal-ric-dashboard>`_
+
+Issues are tracked in the Linux Foundation Jira server:
+
+    `<https://jira.o-ran-sc.org/secure/Dashboard.jspa>`_
+
+Project information is available in the Linux Foundation Wiki:
+
+    `<https://wiki.o-ran-sc.org>`_
+
+
+A1 Mediator
+-----------
+
+The Dashboard interfaces with the A1 Mediator.  This platform
+component is accessed via HTTP/REST requests using a client that is
+generated from an API specification published by the A1 Mediator team.
+
+The A1 Mediator supports fetching and storing configuration of
+applications, which is referred to as getting or setting a policy.
+The Dashboard UI provides screens to view and modify configuration
+data for such applications.  As of this writing, the only application
+that is managed via the A1 Mediator interface is the Admission Control
+("AC") application.
+
+
+Application Manager
+-------------------
+
+The Dashboard interfaces with the Application Manager.  This platform
+component is accessed via HTTP/REST requests using a client that is
+generated from an API specification published by the Application
+Manager team.
+
+The Application Manager supports deploying, undeploying and
+configuring applications in the RIC. The Dashboard UI provides screens
+for these functions.
+
+
+Automatic Neighbor Relation Application
+---------------------------------------
+
+The Dashboard interfaces with the Automatic Neighbor Relation (ANR)
+application.  This RIC application is accessed via HTTP/REST requests
+using a client that is generated from an API specification published
+by the ANR team.
+
+This RIC application discovers and manages the Neighbor Cell Relation
+Table (NCRT). The Dashboard UI provides screens to view and modify
+NCRT data.
+
+
+E2 Manager
+----------
+
+The Dashboard interfaces with the E2 Manager.  This platform
+component is accessed via HTTP/REST requests using a client that is
+generated from an API specification published by the E2 Manager team.
+
+The E2 Manager platform component supports connecting and
+disconnecting RAN elements.  The Dashboard UI provides controls for
+operators to create "ENDC" and "X2" connections, and to disconnect RAN
+elements.
diff --git a/dashboard/docs/release-notes.rst b/dashboard/docs/release-notes.rst
new file mode 100644 (file)
index 0000000..19a9e55
--- /dev/null
@@ -0,0 +1,161 @@
+.. ===============LICENSE_START=======================================================
+.. O-RAN SC CC-BY-4.0
+.. %%
+.. Copyright (C) 2019 AT&T Intellectual Property
+.. %%
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..      http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+.. ===============LICENSE_END=========================================================
+
+RIC Dashboard Release Notes
+===========================
+
+Version 1.2.4, 24 Oct 2019
+--------------------------
+* Revise a1-med-client to use API spec in new submodule ric-plt/a1;
+  removed cached copy
+* Revise app manager client to use API spec in new submodule ric-plt/appmgr;
+  removed cached copy
+* Add Platform page showing Kubernetes pods in aux and platform obtained from CAAS-Ingress
+* Update Angular libraries to recent stable versions
+* Revise user controller to answer data sent by portal, drop the mock implementation
+* Set global style for page titles
+* Align page titles to top left,decrease font size
+* Update EPSDK-FW to version 2.6
+* Make constructor robust to missing caasingress.insecure property
+* Repair bug that omitted slashes in CAAS-Ingress URL builder
+* Improve the dark mode
+* Show container ready count with total count
+
+Version 1.2.3, 4 Oct 2019
+-------------------------
+* Serve unauthenticated user a login-at-portal page without using redirect
+* Upgrade to Spring-Boot 2.1.9.RELEASE
+
+Version 1.2.2, 27 Sep 2019
+--------------------------
+* Support Portal security using EPSDK-FW cookie and user management
+
+Version 1.2.1, 20 Sep 2019
+--------------------------
+* Repair E2 URLs in front end like endc-setup/endcSetup
+* Extend ANR mock feature to persist edits for demos
+* Block whitespace in E2 IP input field validation
+* Relax validation in E2 RAN name field validation
+* Make RAN connection table robust to missing fields
+* Install curl when building Docker image
+
+Version 1.2.0, 11 Sep 2019
+--------------------------
+* Split URL properties into prefix/suffix parts
+* Add jacoco plugin to back-end for code coverage
+* Compile with Java version 11, use base openjdk:11-jre-slim
+* Clean code of issues reported by Sonar
+* Drop mock RAN names feature that supported R1 testing
+* Extend mock endpoints to simulate delay seen in tests
+* Move mock configuration classes into test area
+* Update App manager client to spec version 0.1.7
+* Add controller for page refresh of Angular routes
+* Extend E2 mock configuration for demo purposes
+* Add pattern for matching AC/admin application name
+* Add custom (plain but not white-label) error page
+* Synch A1 method paths in front-end and back-end
+* Add xapp dynamic configuration feature
+* Disable x-frame-options response header
+* Repair app manager undeploy-app back/front methods
+* Display AC xAPP metrics data via Kibana source (metrics.url.ac) on dashboard
+* Pass AC policy parameter without parsing as JSON
+* Use snake_case (not camelCase) names in AC policy front end
+* Update A1 mediator client to spec version 0.10.3
+* Extend AC control screen to read policy from A1
+* Create loading-dialog component and service
+* Showing the loading-dialog while making API call
+* Add notification and error handling for xapp configuration
+* Update E2 manager client to spec version 2.0.5 of 2019-09-11
+* Display MC xAPP metrics data via Kibana source (metrics.url.mc) on dashboard
+
+Version 1.0.5, 5 July 2019
+--------------------------
+* Upgrade to Angular version 8
+* Upgrade to Spring-Boot 2.1.6.RELEASE
+* Align AC xApp policy page title
+* Update E2 manager client to spec version 20190703
+* Add configuration-driven mock of E2 getNodebIdList
+* Revise front-end components to use prefix 'rd'
+* Improve error handling in BE and FE code
+* Revise the notification service to display multiple notifications
+* Add JUnit test cases for controller methods
+
+Version 1.0.4, 27 June 2019
+---------------------------
+* Add AC xApp neighbor control screen
+* Add ANR xApp neighbor cell relation table
+* Drop the pendulum xApp control screen
+* Add column sorting on xApp catalog, xApp control, ANR
+* Add disconnect-all button to RAN connection screen
+* Extend E2 service with disconnect-all method
+* Update ANR xApp client to spec version 0.0.8
+* Update E2 manager client to spec version 20190620
+* Adjust CSS and HTML for main container positioning
+* Revise config property keys to use URL (not basepath)
+* Left menu overlap main content fix
+* Extend back-end controllers to return error details
+* Add feature resilient to malformed instance data
+* Extend Xapp Controller with config endpoints
+* Add build number to dashboard version string
+* Move mock admin screen user data to backend
+* Update App manager client to spec version 0.1.5
+* Move RAN connection feature to control screen
+* Rework admin table
+* Update the notification service
+* Move RAN connection feature to control screen
+* Repair deploy-app feature and use icon instead of text button
+
+Version 1.0.3, 28 May 2019
+--------------------------
+* Add AC xApp controller to backend
+* Add AC xApp interface to frontend
+* Add RAN type radio selector to connection setup
+* Update ANR xApp client to spec version 0.0.7
+* Update E2 manager client to spec version 20190515
+* Update xApp manager client to spec version 0.1.4
+* Add get-version methods to all controllers
+* Add simple page footer with copyright and version
+* Add AC and ANR xApp services
+* Rename signal service to E2 Manager service
+* Use XappMgrService to replace ControlService and CatalogService
+* Apply mat-table to control and catalog
+* RAN Connection screen upgrade to mat-table
+
+Version 1.0.2, 13 May 2019
+--------------------------
+* Update A1 mediator client to version 0.4.0
+* Add E2 response message with timestamp and status code
+* Fetch xAPP instance status information from xAPP Manager and display in dashboard
+* Allow the user to initiate an E2 (X2) connection between RIC and gNB/eNB
+* User input validations on connections between RIC and eNB/gNB in the dashboard
+* Add ANR xApp backend with mock implementation
+* Add undeploy xApp function
+* Add shared confirm dialog
+* Add shared notification
+
+Version 1.0.1, 6 May 2019
+-------------------------
+* Add draft A1 Mediator API definition
+* Use E2 Manager API definition dated 2 May 2019, with tag modifications
+* Adjust group IDs and packages for name O-RAN-SC; drop ORAN-OSC
+* Add ANR API spec and client code generator
+* Update xApp Manager API spec to version 0.1.2
+
+Version 1.0.0, 30 Apr 2019
+--------------------------
+* Initial version
diff --git a/dashboard/docs/requirements-docs.txt b/dashboard/docs/requirements-docs.txt
new file mode 100644 (file)
index 0000000..09a0c1c
--- /dev/null
@@ -0,0 +1,5 @@
+sphinx
+sphinx-rtd-theme
+sphinxcontrib-httpdomain
+recommonmark
+lfdocs-conf
diff --git a/dashboard/pom.xml b/dashboard/pom.xml
new file mode 100644 (file)
index 0000000..7123fef
--- /dev/null
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--<![CDATA[
+========================LICENSE_START=================================
+O-RAN-SC
+%%
+Copyright (C) 2019 AT&T Intellectual Property
+%%
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+========================LICENSE_END===================================
+]]>-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <!-- this group Id must match LF gerrit repository -->
+               <groupId>org.springframework.boot</groupId>
+               <artifactId>spring-boot-starter-parent</artifactId>
+               <version>2.1.9.RELEASE</version>
+               <relativePath /> <!-- lookup parent from repository -->
+       </parent>
+       <groupId>org.o-ran-sc.portal.ric-dashboard</groupId>
+       <artifactId>ric-dash-parent</artifactId>
+       <name>RIC Dashboard project</name>
+       <packaging>pom</packaging>
+       <version>1.2.5-SNAPSHOT</version>
+       <properties>
+               <java.version>11</java.version>
+               <!-- Properties for the license-maven-plugin in child POMs -->
+               <lmp.organization.name>AT&amp;T Intellectual Property</lmp.organization.name>
+               <lmp.project.name>O-RAN-SC</lmp.project.name>
+               <lmp.inception.year>2019</lmp.inception.year>
+               <lmp.license.name>apache_v2</lmp.license.name>
+               <lmp.process.start.tag>========================LICENSE_START=================================</lmp.process.start.tag>
+               <lmp.process.end.tag>========================LICENSE_END===================================</lmp.process.end.tag>
+       </properties>
+       <modules>
+               <module>a1-controller-client</module>
+               <module>webapp-frontend</module>
+               <module>webapp-backend</module>
+       </modules>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.codehaus.mojo</groupId>
+                               <artifactId>license-maven-plugin</artifactId>
+                               <configuration>
+                                       <organizationName>${lmp.organization.name}</organizationName>
+                                       <inceptionYear>${lmp.inception.year}</inceptionYear>
+                                       <projectName>${lmp.project.name}</projectName>
+                                       <licenseName>${lmp.license.name}</licenseName>
+                                       <processStartTag>${lmp.process.start.tag}</processStartTag>
+                                       <processEndTag>${lmp.process.end.tag}</processEndTag>
+                                       <addJavaLicenseAfterPackage>false</addJavaLicenseAfterPackage>
+                               </configuration>
+                               <executions>
+                                       <execution>
+                                               <id>first</id>
+                                               <goals>
+                                                       <goal>update-file-header</goal>
+                                               </goals>
+                                               <phase>process-sources</phase>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-compiler-plugin</artifactId>
+                               <configuration>
+                                       <source>${java.version}</source>
+                                       <target>${java.version}</target>
+                               </configuration>
+                       </plugin>
+                       <!-- Always generate a source jar -->
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-source-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <id>attach-sources</id>
+                                               <goals>
+                                                       <goal>jar</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <!-- Always skip the deploy-jar-to-nexus step -->
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-deploy-plugin</artifactId>
+                               <configuration>
+                                       <skip>true</skip>
+                               </configuration>
+                       </plugin>
+                       <!-- support sonar in multi-module project -->
+                       <plugin>
+                               <groupId>org.sonarsource.scanner.maven</groupId>
+                               <artifactId>sonar-maven-plugin</artifactId>
+                               <version>3.6.0.1398</version>
+                       </plugin>
+               </plugins>
+               <pluginManagement>
+                       <plugins>
+                               <plugin>
+                                       <groupId>org.codehaus.mojo</groupId>
+                                       <artifactId>license-maven-plugin</artifactId>
+                                       <version>1.20</version>
+                               </plugin>
+                       </plugins>
+               </pluginManagement>
+       </build>
+</project>
diff --git a/dashboard/tox.ini b/dashboard/tox.ini
new file mode 100644 (file)
index 0000000..72742cf
--- /dev/null
@@ -0,0 +1,30 @@
+# documentation only
+[tox]
+minversion = 2.0
+envlist =
+    docs,
+    docs-linkcheck,
+skipsdist = true
+
+[testenv:docs]
+basepython = python3
+deps = 
+    sphinx
+    sphinx-rtd-theme
+    sphinxcontrib-httpdomain
+    recommonmark
+    lfdocs-conf
+    
+commands =
+    sphinx-build -W -b html -n -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/html
+    echo "Generated docs available in {toxinidir}/docs/_build/html"
+whitelist_externals = echo
+
+[testenv:docs-linkcheck]
+basepython = python3
+deps = sphinx
+       sphinx-rtd-theme
+       sphinxcontrib-httpdomain
+       recommonmark
+       lfdocs-conf
+commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck
diff --git a/dashboard/webapp-backend/.gitignore b/dashboard/webapp-backend/.gitignore
new file mode 100644 (file)
index 0000000..02dbd44
--- /dev/null
@@ -0,0 +1,34 @@
+/.classpath
+/.project
+/.settings
+/target/
+/logs/
+/.mvn/wrapper/maven-wrapper.jar
+/logs/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+/application-tlab2.properties
+/application.properties
+/dashboard-users.json
diff --git a/dashboard/webapp-backend/README.md b/dashboard/webapp-backend/README.md
new file mode 100644 (file)
index 0000000..5dd1b1e
--- /dev/null
@@ -0,0 +1,25 @@
+# RIC Dashboard Web Application Backend
+
+The RIC Dashboard back-end provides REST services to the Dashboard
+front-end Typescript features running in the user's browser.  For
+production use, it also serves the Angular application files.
+
+Please see the documentation in the docs/ folder.
+
+The backend server publishes live API documentation at the
+URL `http://your-host-name-here:8080/swagger-ui.html`
+
+## License
+
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/dashboard/webapp-backend/config/.gitignore b/dashboard/webapp-backend/config/.gitignore
new file mode 100644 (file)
index 0000000..edd66f1
--- /dev/null
@@ -0,0 +1,2 @@
+/key.properties
+/portal.properties
diff --git a/dashboard/webapp-backend/config/key.properties.template b/dashboard/webapp-backend/config/key.properties.template
new file mode 100644 (file)
index 0000000..a8d44e3
--- /dev/null
@@ -0,0 +1,21 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides a secret key for the RIC Dashboard.
+
+cipher.enc.key =
diff --git a/dashboard/webapp-backend/config/portal.properties.template b/dashboard/webapp-backend/config/portal.properties.template
new file mode 100644 (file)
index 0000000..8c7fec7
--- /dev/null
@@ -0,0 +1,34 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+# The following properties are the same in every deployment
+
+portal.api.impl.class = org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+
+# The following properties are DIFFERENT in every deployment
+
+# URL of portal login screen
+ecomp_redirect_url = http://localhost/portal
+# URL of portal API
+ecomp_rest_url = http://localhost/portal
+# Value assigned by portal instance
+ueb_app_key = abcdef1234567890
diff --git a/dashboard/webapp-backend/pom.xml b/dashboard/webapp-backend/pom.xml
new file mode 100644 (file)
index 0000000..ec4fdea
--- /dev/null
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--<![CDATA[
+========================LICENSE_START=================================
+O-RAN-SC
+%%
+Copyright (C) 2019 AT&T Intellectual Property
+%%
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+========================LICENSE_END===================================
+]]>-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.o-ran-sc.portal.ric-dashboard</groupId>
+               <artifactId>ric-dash-parent</artifactId>
+               <version>1.2.5-SNAPSHOT</version>
+       </parent>
+       <artifactId>ric-dash-be</artifactId>
+       <name>RIC Dashboard Webapp backend</name>
+       <properties>
+               <springfox.version>2.9.2</springfox.version>
+               <!-- Set by Jenkins -->
+               <build.number>0</build.number>
+       </properties>
+       <repositories>
+               <repository>
+                       <id>onap-releases</id>
+                       <name>ONAP - Release Repository</name>
+                       <url>https://nexus.onap.org/content/repositories/releases</url>
+               </repository>
+       </repositories>
+       <dependencies>
+               <!-- Platform components -->
+               <dependency>
+                       <groupId>org.o-ran-sc.ric.plt.a1controller.client</groupId>
+                       <artifactId>a1-controller-client</artifactId>
+                       <version>0.1.0-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.onap.portal.sdk</groupId>
+                       <artifactId>epsdk-fw</artifactId>
+                       <version>2.6.0</version>
+                       <exclusions>
+                               <exclusion>
+                                       <groupId>commons-logging</groupId>
+                                       <artifactId>commons-logging</artifactId>
+                               </exclusion>
+                               <exclusion>
+                                       <groupId>log4j</groupId>
+                                       <artifactId>log4j</artifactId>
+                               </exclusion>
+                               <exclusion>
+                                       <groupId>log4j</groupId>
+                                       <artifactId>apache-log4j-extras</artifactId>
+                               </exclusion>
+                               <exclusion>
+                                       <groupId>org.slf4j</groupId>
+                                       <artifactId>slf4j-log4j12</artifactId>
+                               </exclusion>
+                               <exclusion>
+                                       <groupId>junit</groupId>
+                                       <artifactId>junit</artifactId>
+                               </exclusion>
+                               <exclusion>
+                                       <groupId>commons-fileupload</groupId>
+                                       <artifactId>commons-fileupload</artifactId>
+                               </exclusion>
+                               <exclusion>
+                                       <groupId>commons-beanutils</groupId>
+                                       <artifactId>commons-beanutils</artifactId>
+                               </exclusion>
+                               <!-- EELF omits "test" scope on this dependency -->
+                               <exclusion>
+                                       <groupId>org.powermock</groupId>
+                                       <artifactId>powermock-module-junit4</artifactId>
+                               </exclusion>
+                               <!-- EELF omits "test" scope on this dependency -->
+                               <exclusion>
+                                       <groupId>org.powermock</groupId>
+                                       <artifactId>powermock-api-mockito</artifactId>
+                               </exclusion>
+                       </exclusions>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-security</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-web</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.slf4j</groupId>
+                       <artifactId>slf4j-api</artifactId>
+               </dependency>
+               <!-- Bridge uses of Apache commons logging, like EPSDK-FW -->
+               <dependency>
+                       <groupId>org.slf4j</groupId>
+                       <artifactId>jcl-over-slf4j</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>ch.qos.logback</groupId>
+                       <artifactId>logback-classic</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>ch.qos.logback</groupId>
+                       <artifactId>logback-core</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>io.springfox</groupId>
+                       <artifactId>springfox-swagger2</artifactId>
+                       <version>${springfox.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>io.springfox</groupId>
+                       <artifactId>springfox-swagger-ui</artifactId>
+                       <version>${springfox.version}</version>
+               </dependency>
+               <!-- Test dependencies -->
+               <!-- Mockito supports development, not just testing -->
+               <dependency>
+                       <groupId>org.mockito</groupId>
+                       <artifactId>mockito-core</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.springframework.boot</groupId>
+                       <artifactId>spring-boot-starter-test</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.junit.jupiter</groupId>
+                       <artifactId>junit-jupiter-api</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.junit.jupiter</groupId>
+                       <artifactId>junit-jupiter-engine</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.junit.platform</groupId>
+                       <artifactId>junit-platform-launcher</artifactId>
+                       <!-- Override Spring-Boot choice for Eclipse -->
+                       <version>1.4.2</version>
+                       <scope>test</scope>
+               </dependency>
+       </dependencies>
+       <build>
+               <plugins>
+                       <plugin>
+                               <groupId>org.springframework.boot</groupId>
+                               <artifactId>spring-boot-maven-plugin</artifactId>
+                       </plugin>
+                       <plugin>
+                               <!-- Most configuration and all execution is inherited -->
+                               <groupId>org.codehaus.mojo</groupId>
+                               <artifactId>license-maven-plugin</artifactId>
+                               <configuration>
+                                       <roots>
+                                               <root>src</root>
+                                       </roots>
+                                       <excludes>
+                                               <exclude>**/*.json</exclude>
+                                       </excludes>
+                               </configuration>
+                       </plugin>
+                       <!-- Add the build number to the jar manifest. Spring-Boot uses a complex 
+                               packaging process that makes access to the original Manifest.MF very difficult. 
+                               However, Java provides access to the implementation version for a package, 
+                               so cram the build number into there. -->
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-jar-plugin</artifactId>
+                               <configuration>
+                                       <archive>
+                                               <manifest>
+                                                       <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                                               </manifest>
+                                               <manifestEntries>
+                                                       <Implementation-Version>${project.version}-b${build.number}</Implementation-Version>
+                                               </manifestEntries>
+                                       </archive>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <artifactId>maven-resources-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <id>copy-resources</id>
+                                               <phase>validate</phase>
+                                               <goals>
+                                                       <goal>copy-resources</goal>
+                                               </goals>
+                                               <configuration>
+                                                       <outputDirectory>${project.build.directory}/classes/resources/</outputDirectory>
+                                                       <resources>
+                                                               <resource>
+                                                                       <directory>${project.parent.basedir}/webapp-frontend/dist/dashApp/</directory>
+                                                               </resource>
+                                                       </resources>
+                                               </configuration>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <!-- do not deploy a jar or pom file -->
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-deploy-plugin</artifactId>
+                               <configuration>
+                                       <skip>true</skip>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.jacoco</groupId>
+                               <artifactId>jacoco-maven-plugin</artifactId>
+                               <version>0.8.4</version>
+                               <executions>
+                                       <execution>
+                                               <id>default-prepare-agent</id>
+                                               <goals>
+                                                       <goal>prepare-agent</goal>
+                                               </goals>
+                                       </execution>
+                                       <execution>
+                                               <id>default-report</id>
+                                               <phase>prepare-package</phase>
+                                               <goals>
+                                                       <goal>report</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <!-- https://stackoverflow.com/questions/39126226/fabric8-springboot-full-example -->
+                       <plugin>
+                               <groupId>io.fabric8</groupId>
+                               <artifactId>docker-maven-plugin</artifactId>
+                               <version>0.30.0</version>
+                               <configuration>
+                                       <verbose>true</verbose>
+                                       <!-- environment variables supplied by Jenkins -->
+                                       <pullRegistry>${env.CONTAINER_PULL_REGISTRY}</pullRegistry>
+                                       <pushRegistry>${env.CONTAINER_PUSH_REGISTRY}</pushRegistry>
+                                       <images>
+                                               <image>
+                                                       <!-- Specify a tag to avoid default tag "latest" -->
+                                                       <!-- Avoid maven artifact name here -->
+                                                       <name>ric-dashboard:${project.version}</name>
+                                                       <build>
+                                                               <from>openjdk:11-jre-slim</from>
+                                                               <tags>
+                                                                       <!-- Add tag with build number -->
+                                                                       <tag>${project.version}</tag>
+                                                               </tags>
+                                                               <assembly>
+                                                                       <descriptorRef>artifact</descriptorRef>
+                                                               </assembly>
+                                                               <runCmds>
+                                                                       <!-- Ensure logs dir exists and is world writable -->
+                                                                       <runCmd>mkdir /logs</runCmd>
+                                                                       <runCmd>chmod -R 777 /logs</runCmd>
+                                                               </runCmds>
+                                                               <cmd>
+                                                                       <!-- Include maven dir on classpath for prop files -->
+                                                                       <exec>
+                                                                               <arg>java</arg>
+                                                                               <arg>-Xms128m</arg>
+                                                                               <arg>-Xmx256m</arg>
+                                                                               <arg>-cp</arg>
+                                                                               <arg>maven:maven/${project.artifactId}-${project.version}.${project.packaging}</arg>
+                                                                               <arg>-Dloader.main=org.oransc.ric.portal.dashboard.DashboardApplication</arg>
+                                                                               <arg>-Djava.security.egd=file:/dev/./urandom</arg>
+                                                                               <arg>org.springframework.boot.loader.PropertiesLauncher</arg>
+                                                                       </exec>
+                                                               </cmd>
+                                                       </build>
+                                               </image>
+                                       </images>
+                               </configuration>
+                               <!-- build Docker images in install phase, push in deploy phase -->
+                               <executions>
+                                       <execution>
+                                               <goals>
+                                                       <goal>build</goal>
+                                                       <goal>push</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+               </plugins>
+       </build>
+</project>
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardApplication.java
new file mode 100644 (file)
index 0000000..333c532
--- /dev/null
@@ -0,0 +1,60 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.ric.portal.dashboard;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+// Limit scan to dashboard classes; exclude generated API classes
+@ComponentScan("org.oransc.ric.portal.dashboard")
+public class DashboardApplication {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       public static void main(String[] args) throws IOException {
+               SpringApplication.run(DashboardApplication.class, args);
+               // Ensure this appears on the console by using level WARN
+               logger.warn("main: version '{}' successful start",
+                               getImplementationVersion(MethodHandles.lookup().lookupClass()));
+       }
+
+       /**
+        * Gets version details for the specified class.
+        * 
+        * @param clazz
+        *                  Class to get the version
+        * 
+        * @return the value of the MANIFEST.MF property Implementation-Version as
+        *         written by maven when packaged in a jar; 'unknown' otherwise.
+        */
+       public static String getImplementationVersion(Class<?> clazz) {
+               String classPath = clazz.getResource(clazz.getSimpleName() + ".class").toString();
+               return classPath.startsWith("jar") ? clazz.getPackage().getImplementationVersion() : "unknown-not-jar";
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardConstants.java
new file mode 100644 (file)
index 0000000..d61ce1d
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard;
+
+public abstract class DashboardConstants {
+
+       private DashboardConstants() {
+               // Sonar insists on hiding the constructor
+       }
+
+       public static final String ENDPOINT_PREFIX = "/api";
+       // Factor out method names used in multiple controllers
+       public static final String VERSION_METHOD = "version";
+       public static final String APP_NAME_AC = "AC";
+       public static final String APP_NAME_MC = "MC";
+       // The role names are defined by ONAP Portal.
+       // The prefix "ROLE_" is required by Spring.
+       // These are used in Java code annotations that require constants.
+       public static final String ROLE_NAME_STANDARD = "Standard_User";
+       public static final String ROLE_NAME_ADMIN = "System_Administrator";
+       private static final String ROLE_PREFIX = "ROLE_";
+       public static final String ROLE_ADMIN = ROLE_PREFIX + ROLE_NAME_ADMIN;
+       public static final String ROLE_STANDARD = ROLE_PREFIX + ROLE_NAME_STANDARD;
+       public static final String A1_CONTROLLER_USERNAME = "admin";
+       public static final String A1_CONTROLLER_PASSWORD = "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U";
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardUserManager.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/DashboardUserManager.java
new file mode 100644 (file)
index 0000000..c5fd101
--- /dev/null
@@ -0,0 +1,184 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Provides simple user-management services.
+ * 
+ * This first implementation serializes user details to a file.
+ * 
+ * TODO: migrate to a database.
+ */
+public class DashboardUserManager {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // This default value is only useful for development and testing.
+       public static final String USER_FILE_PATH = "dashboard-users.json";
+
+       private final File userFile;
+       private final List<EcompUser> users;
+
+       /**
+        * Development/test-only constructor that uses default file path.
+        * 
+        * @param clear
+        *                  If true, start empty and remove any existing file.
+        * 
+        * @throws IOException
+        *                         On file error
+        */
+       public DashboardUserManager(boolean clear) throws IOException {
+               this(USER_FILE_PATH);
+               if (clear) {
+                       logger.debug("ctor: removing file {}", userFile.getAbsolutePath());
+                       File f = new File(DashboardUserManager.USER_FILE_PATH);
+                       if (f.exists())
+                               f.delete();
+                       users.clear();
+               }
+       }
+
+       /**
+        * Constructur that accepts a file path
+        * 
+        * @param userFilePath
+        *                         File path
+        * @throws IOException
+        *                         If file cannot be read
+        */
+       public DashboardUserManager(final String userFilePath) throws IOException {
+               logger.debug("ctor: userfile {}", userFilePath);
+               if (userFilePath == null)
+                       throw new IllegalArgumentException("Missing or empty user file property");
+               userFile = new File(userFilePath);
+               logger.debug("ctor: managing users in file {}", userFile.getAbsolutePath());
+               if (userFile.exists()) {
+                       final ObjectMapper mapper = new ObjectMapper();
+                       users = mapper.readValue(userFile, new TypeReference<List<EcompUser>>() {
+                       });
+               } else {
+                       users = new ArrayList<>();
+               }
+       }
+
+       /**
+        * Gets the current users.
+        * 
+        * @return List of EcompUser objects, possibly empty
+        */
+       public List<EcompUser> getUsers() {
+               return this.users;
+       }
+
+       /**
+        * Gets the user with the specified login Id
+        * 
+        * @param loginId
+        *                    Desired login Id
+        * @return User object; null if Id is not known
+        */
+       public EcompUser getUser(String loginId) {
+               for (EcompUser u : this.users) {
+                       if (u.getLoginId().equals(loginId)) {
+                               logger.debug("getUser: match on {}", loginId);
+                               return u;
+                       }
+               }
+               logger.debug("getUser: no match on {}", loginId);
+               return null;
+       }
+
+       private void saveUsers() throws JsonGenerationException, JsonMappingException, IOException {
+               final ObjectMapper mapper = new ObjectMapper();
+               mapper.writeValue(userFile, users);
+       }
+
+       /*
+        * Allow at most one thread to create a user at one time.
+        */
+       public synchronized void createUser(EcompUser user) throws PortalAPIException {
+               logger.debug("createUser: loginId is " + user.getLoginId());
+               if (users.contains(user))
+                       throw new PortalAPIException("User exists: " + user.getLoginId());
+               users.add(user);
+               try {
+                       saveUsers();
+               } catch (Exception ex) {
+                       throw new PortalAPIException("Save failed", ex);
+               }
+       }
+
+       /*
+        * Allow at most one thread to modify a user at one time. We still have
+        * last-edit-wins of course.
+        */
+       public synchronized void updateUser(String loginId, EcompUser user) throws PortalAPIException {
+               logger.debug("editUser: loginId is " + loginId);
+               int index = users.indexOf(user);
+               if (index < 0)
+                       throw new PortalAPIException("User does not exist: " + user.getLoginId());
+               users.remove(index);
+               users.add(user);
+               try {
+                       saveUsers();
+               } catch (Exception ex) {
+                       throw new PortalAPIException("Save failed", ex);
+               }
+       }
+
+       // Test infrastructure
+       public static void main(String[] args) throws Exception {
+               DashboardUserManager dum = new DashboardUserManager(false);
+               EcompUser user = new EcompUser();
+               user.setActive(true);
+               user.setLoginId("demo");
+               user.setFirstName("First");
+               user.setLastName("Last");
+               EcompRole role = new EcompRole();
+               role.setId(1L);
+               role.setName(DashboardConstants.ROLE_NAME_ADMIN);
+               Set<EcompRole> roles = new HashSet<>();
+               roles.add(role);
+               user.setRoles(roles);
+               dum.createUser(user);
+               logger.debug("Created user {}", user);
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1ControllerConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/A1ControllerConfiguration.java
new file mode 100644 (file)
index 0000000..ffdcacd
--- /dev/null
@@ -0,0 +1,74 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import java.lang.invoke.MethodHandles;
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.invoker.ApiClient;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+
+/**
+ * Creates an A1 controller client as a bean to be managed by the Spring
+ * container.
+ */
+@Configuration
+@Profile("!test")
+public class A1ControllerConfiguration {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+       public static final String A1_CONTROLLER_USERNAME = DashboardConstants.A1_CONTROLLER_USERNAME;
+       public static final String A1_CONTROLLER_PASSWORD = DashboardConstants.A1_CONTROLLER_PASSWORD;
+
+       // Populated by the autowired constructor
+       private final String a1ControllerUrl;
+
+       @Autowired
+       public A1ControllerConfiguration(@Value("${a1controller.url.prefix}") final String urlPrefix, //
+                       @Value("${a1controller.url.suffix}") final String urlSuffix) {
+               logger.debug("ctor prefix '{}' suffix '{}'", urlPrefix, urlSuffix);
+               a1ControllerUrl = new DefaultUriBuilderFactory(urlPrefix.trim()).builder().path(urlSuffix.trim()).build().normalize()
+                               .toString();
+               logger.info("Configuring A1 Controller at URL {}", a1ControllerUrl);
+       }
+
+       private ApiClient apiClient() {
+               ApiClient apiClient = new ApiClient(new RestTemplate());
+               apiClient.setBasePath(a1ControllerUrl);
+               apiClient.setUsername(A1_CONTROLLER_USERNAME);
+               apiClient.setPassword(A1_CONTROLLER_PASSWORD);
+               return apiClient;
+       }
+
+       @Bean
+       // The bean (method) name must be globally unique
+       public A1ControllerApi a1ControllerApi() {
+               return new A1ControllerApi(apiClient());
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AdminConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/AdminConfiguration.java
new file mode 100644 (file)
index 0000000..696d74f
--- /dev/null
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * Creates an instance of the user manager.
+ */
+@Configuration
+@Profile("!test")
+public class AdminConfiguration {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // Populated by the autowired constructor
+       private final String userfile;
+
+       @Autowired
+       public AdminConfiguration(@Value("${userfile}") final String userfile) {
+               logger.debug("ctor userfile '{}'", userfile);
+               this.userfile = userfile;
+       }
+
+       @Bean
+       // The bean (method) name must be globally unique
+       public DashboardUserManager userManager() throws IOException {
+               return new DashboardUserManager(userfile);
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/PortalApiConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/PortalApiConfiguration.java
new file mode 100644 (file)
index 0000000..f318cad
--- /dev/null
@@ -0,0 +1,57 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import java.lang.invoke.MethodHandles;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Configuration
+@Profile("!test")
+public class PortalApiConfiguration {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       /**
+        * Instantiates the EPSDK-FW servlet that implements the API called by Portal.
+        * Needed because this app is not configured to scan the EPSDK-FW packages;
+        * there's also a chance that Spring-Boot does not automatically
+        * process @WebServlet annotations.
+        * 
+        * @return Servlet registration bean for the Portal Rest API proxy servlet.
+        */
+       @Bean
+       public ServletRegistrationBean<PortalRestAPIProxy> portalApiProxyServletBean() {
+               logger.debug("portalApiProxyServletBean");
+               PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+               final ServletRegistrationBean<PortalRestAPIProxy> servletBean = new ServletRegistrationBean<>(servlet,
+                               PortalApiConstants.API_PREFIX + "/*");
+               servletBean.setName("PortalRestApiProxyServlet");
+               return servletBean;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SpringContextCache.java
new file mode 100644 (file)
index 0000000..3a87782
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Allows non-Spring-managed classes to obtain the Spring application context.
+ */
+@Component
+public class SpringContextCache implements ApplicationContextAware {
+
+       private static ApplicationContext applicationContext = null;
+
+       @Override
+       public void setApplicationContext(final ApplicationContext appContext) throws BeansException {
+               applicationContext = appContext;
+       }
+
+       public static ApplicationContext getApplicationContext() {
+               return applicationContext;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SwaggerConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/SwaggerConfiguration.java
new file mode 100644 (file)
index 0000000..b4bb0a3
--- /dev/null
@@ -0,0 +1,68 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.ric.portal.dashboard.config;
+
+import org.oransc.ric.portal.dashboard.DashboardApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * http://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfiguration {
+
+       /**
+        * @return new Docket
+        */
+       @Bean
+       public Docket api() {
+               return new Docket(DocumentationType.SWAGGER_2).select() //
+                               .apis(RequestHandlerSelectors.basePackage(DashboardApplication.class.getPackage().getName())) //
+                               .paths(PathSelectors.any()) //
+                               .build() //
+                               .apiInfo(apiInfo());
+       }
+
+       private ApiInfo apiInfo() {
+               final String version = DashboardApplication.class.getPackage().getImplementationVersion();
+               return new ApiInfoBuilder() //
+                               .title("RIC Dashboard backend") //
+                               .description("Proxies access to RIC services.")//
+                               .termsOfServiceUrl("Terms of service") //
+                               .contact(new Contact("RIC Dashboard Dev Team", //
+                                               "http://no-docs-yet.org/", //
+                                               "noreply@O-RAN-SC.org")) //
+                               .license("Apache 2.0 License").licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") //
+                               .version(version == null ? "version not available" : version) //
+                               .build();
+       }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/config/WebSecurityConfiguration.java
new file mode 100644 (file)
index 0000000..b912500
--- /dev/null
@@ -0,0 +1,125 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.controller.A1Controller;
+import org.oransc.ric.portal.dashboard.controller.SimpleErrorController;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthManager;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("!test")
+public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // Although constructor arguments are recommended over field injection,
+       // this results in fewer lines of code.
+       @Value("${portalapi.security}")
+       private Boolean portalapiSecurity;
+       @Value("${portalapi.appname}")
+       private String appName;
+       @Value("${portalapi.username}")
+       private String userName;
+       @Value("${portalapi.password}")
+       private String password;
+       @Value("${portalapi.decryptor}")
+       private String decryptor;
+       @Value("${portalapi.usercookie}")
+       private String userCookie;
+
+       @Autowired
+       DashboardUserManager userManager;
+
+       @Override
+    protected void configure(HttpSecurity http) throws Exception {
+               logger.debug("configure: portalapi.username {}", userName);
+               // A chain of ".and()" always baffles me
+               http.authorizeRequests().anyRequest().authenticated();
+               http.headers().frameOptions().disable();
+               http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+               http.addFilterBefore(portalAuthenticationFilterBean(), BasicAuthenticationFilter.class);
+       }
+
+       /**
+        * Resource paths that do not require authentication, especially including
+        * Swagger-generated documentation.
+        */
+       public static final String[] OPEN_PATHS = { //
+                       "/v2/api-docs", //
+                       "/swagger-resources/**", //
+                       "/swagger-ui.html", //
+                       "/webjars/**", //
+                       PortalApiConstants.API_PREFIX + "/**", //
+                       A1Controller.CONTROLLER_PATH + "/" + A1Controller.VERSION_METHOD, //                                    
+                       SimpleErrorController.ERROR_PATH };
+
+       @Override
+       public void configure(WebSecurity web) throws Exception {
+               // This disables Spring security, but not the app's filter.
+               web.ignoring().antMatchers(OPEN_PATHS);
+       }
+
+       @Bean
+       public PortalAuthManager portalAuthManagerBean()
+                       throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
+                       IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
+               return new PortalAuthManager(appName, userName, password, decryptor, userCookie);
+       }
+
+       /*
+        * If this is annotated with @Bean, it is created automatically AND REGISTERED,
+        * and Spring processes annotations in the source of the class. However, the
+        * filter is added in the chain apparently in the wrong order. Alternately, with
+        * no @Bean and added to the chain up in the configure() method in the desired
+        * order, the ignoring() matcher pattern configured above causes Spring to
+        * bypass this filter, which seems to me means the filter participates
+        * correctly.
+        */
+       public PortalAuthenticationFilter portalAuthenticationFilterBean()
+                       throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException,
+                       IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
+               PortalAuthenticationFilter portalAuthenticationFilter = new PortalAuthenticationFilter(portalapiSecurity,
+                               portalAuthManagerBean(), this.userManager);
+               return portalAuthenticationFilter;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/A1Controller.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/A1Controller.java
new file mode 100644 (file)
index 0000000..386d43c
--- /dev/null
@@ -0,0 +1,220 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidSchemaInput;
+import org.oransc.ric.a1controller.client.model.InputNRRidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidSchemaInput;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchema;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPISchema;
+import org.oransc.ric.a1controller.client.model.OutputPIidsListSchema;
+import org.oransc.ric.a1controller.client.model.OutputPTidsListSchema;
+import org.oransc.ric.portal.dashboard.DashboardApplication;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.model.PolicyInstance;
+import org.oransc.ric.portal.dashboard.model.PolicyInstances;
+import org.oransc.ric.portal.dashboard.model.PolicyType;
+import org.oransc.ric.portal.dashboard.model.PolicyTypes;
+import org.oransc.ric.portal.dashboard.model.SuccessTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * Proxies calls from the front end to the A1 Controller via the A1 Mediator
+ * API.
+ *
+ * If a method throws RestClientResponseException, it is handled by
+ * {@link CustomResponseEntityExceptionHandler#handleProxyMethodException(Exception,
+ * org.springframework.web.context.request.WebRequest)}
+ * which returns status 502. All other exceptions are handled by Spring which
+ * returns status 500.
+ */
+@RestController
+@RequestMapping(value = A1Controller.CONTROLLER_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
+public class A1Controller {
+
+    private static final String NEAR_RT_RIC_ID = "NearRtRic1";
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // Publish paths in constants so tests are easy to write
+       public static final String CONTROLLER_PATH = DashboardConstants.ENDPOINT_PREFIX + "/policy";
+       // Endpoints
+       public static final String VERSION_METHOD = DashboardConstants.VERSION_METHOD;
+       public static final String POLICY_TYPES_METHOD = "policytypes";
+       public static final String POLICY_TYPE_ID_NAME = "policy_type_id";
+       public static final String POLICIES_NAME = "policies";
+       public static final String POLICY_INSTANCE_ID_NAME = "policy_instance_id";
+
+       // Populated by the autowired constructor
+       private final A1ControllerApi a1ControllerApi;
+
+       @Autowired
+       public A1Controller(final A1ControllerApi A1ControllerApi) {
+               Assert.notNull(A1ControllerApi, "API must not be null");
+               this.a1ControllerApi = A1ControllerApi;
+               if (logger.isDebugEnabled())
+                       logger.debug("ctor: configured with client type {}", A1ControllerApi.getClass().getName());
+       }
+
+       @ApiOperation(value = "Gets the A1 client library MANIFEST.MF property Implementation-Version.",
+               response = SuccessTransport.class)
+       @GetMapping(VERSION_METHOD)
+       // No role required
+       public SuccessTransport getA1ControllerClientVersion() {
+               return new SuccessTransport(200, DashboardApplication.getImplementationVersion(A1ControllerApi.class));
+       }
+
+       /*
+        * The fields are defined in the A1Control Typescript interface.
+        */
+       @ApiOperation(value = "Gets the policy types from Near Realtime-RIC via the A1 Controller API")
+       @GetMapping(POLICY_TYPES_METHOD)
+       @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
+       public Object getAllPolicyTypes(HttpServletResponse response) {
+               logger.debug("getAllPolicyTypes");
+               InputNRRidSchemaInput nrrid = new InputNRRidSchemaInput();
+               nrrid.setNearRtRicId(NEAR_RT_RIC_ID);
+               InputNRRidSchema inputSchema = new InputNRRidSchema();
+               inputSchema.setInput(nrrid);
+               OutputPTidsListSchema outputPTidsListSchema =
+                       a1ControllerApi.a1ControllerGetAllPolicyTypes(inputSchema);
+               List<Integer> policyTypeIds = outputPTidsListSchema.getOutput().getPolicyTypeIdList();
+               PolicyTypes policyTypes = new PolicyTypes();
+               InputNRRidPTidSchema typeSchema = new InputNRRidPTidSchema();
+               InputNRRidPTidSchemaInput typeId = new InputNRRidPTidSchemaInput();
+               typeId.setNearRtRicId(NEAR_RT_RIC_ID);
+               for (Integer policyTypeId : policyTypeIds) {
+                       typeId.setPolicyTypeId(policyTypeId);
+                       typeSchema.setInput(typeId);
+                       OutputDescNamePTSchema controllerGetPolicyType =
+                               a1ControllerApi.a1ControllerGetPolicyType(typeSchema);
+                       OutputDescNamePTSchemaOutput policyTypeSchema = controllerGetPolicyType.getOutput();
+                       PolicyType type = new PolicyType(policyTypeId, policyTypeSchema.getName(),
+                                       policyTypeSchema.getDescription(), policyTypeSchema.getPolicyType().toString());
+                       policyTypes.add(type);
+               }
+               return policyTypes;
+       }
+
+       @ApiOperation(value = "Returns the policy instances for the given policy type.")
+       @GetMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME)
+       @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
+       public Object getPolicyInstances(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString) {
+               logger.debug("getPolicyInstances {}", policyTypeIdString);
+               InputNRRidPTidSchemaInput typeIdInput = new InputNRRidPTidSchemaInput();
+               typeIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+               Integer policyTypeId = Integer.decode(policyTypeIdString);
+               typeIdInput.setPolicyTypeId(policyTypeId);
+               InputNRRidPTidSchema inputSchema = new InputNRRidPTidSchema();
+               inputSchema.setInput(typeIdInput);
+               OutputPIidsListSchema controllerGetAllInstancesForType =
+                       a1ControllerApi.a1ControllerGetAllInstancesForType(inputSchema);
+               List<String> instancesForType = controllerGetAllInstancesForType.getOutput().getPolicyInstanceIdList();
+               PolicyInstances instances = new PolicyInstances();
+               InputNRRidPTidPIidSchemaInput instanceIdInput = new InputNRRidPTidPIidSchemaInput();
+               instanceIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+               instanceIdInput.setPolicyTypeId(policyTypeId);
+               InputNRRidPTidPIidSchema instanceInputSchema = new InputNRRidPTidPIidSchema();
+               for (String instanceId : instancesForType) {
+                       instanceIdInput.setPolicyInstanceId(instanceId);
+                       instanceInputSchema.setInput(instanceIdInput);
+                       OutputPISchema policyInstance =
+                               a1ControllerApi.a1ControllerGetPolicyInstance(instanceInputSchema);
+                       PolicyInstance instance =
+                               new PolicyInstance(instanceId, policyInstance.getOutput().getPolicyInstance());
+                       instances.add(instance);
+               }
+               return instances;
+       }
+
+       @ApiOperation(value = "Returns a policy instance of a type")
+       @GetMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{"
+               + POLICY_INSTANCE_ID_NAME + "}")
+       @Secured({ DashboardConstants.ROLE_ADMIN, DashboardConstants.ROLE_STANDARD })
+       public Object getPolicyInstance(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString,
+                       @PathVariable(POLICY_INSTANCE_ID_NAME) String policyInstanceId) {
+               logger.debug("getPolicyInstance {}:{}", policyTypeIdString, policyInstanceId);
+               InputNRRidPTidPIidSchemaInput instanceIdInput = new InputNRRidPTidPIidSchemaInput();
+               instanceIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+               instanceIdInput.setPolicyTypeId(Integer.decode(policyTypeIdString));
+               instanceIdInput.setPolicyInstanceId(policyInstanceId);
+               InputNRRidPTidPIidSchema inputSchema = new InputNRRidPTidPIidSchema();
+               inputSchema.setInput(instanceIdInput);
+               OutputPISchema policyInstance = a1ControllerApi.a1ControllerGetPolicyInstance(inputSchema);
+               return policyInstance.getOutput().getPolicyInstance();
+       }
+
+       @ApiOperation(value = "Creates the policy instances for the given policy type.")
+       @PutMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{"
+               + POLICY_INSTANCE_ID_NAME + "}")
+       @Secured({ DashboardConstants.ROLE_ADMIN })
+       public void putPolicyInstance(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString,
+                       @PathVariable(POLICY_INSTANCE_ID_NAME) String policyInstanceId, @RequestBody String instance) {
+               logger.debug("putPolicyInstance typeId: {}, instanceId: {}, instance: {}", policyTypeIdString,
+                       policyInstanceId, instance);
+               InputNRRidPTidPIidPISchemaInput createInstanceInput = new InputNRRidPTidPIidPISchemaInput();
+               createInstanceInput.setNearRtRicId(NEAR_RT_RIC_ID);
+               createInstanceInput.setPolicyTypeId(Integer.decode(policyTypeIdString));
+               createInstanceInput.setPolicyInstanceId(policyInstanceId);
+               createInstanceInput.setPolicyInstance(instance);
+               InputNRRidPTidPIidPISchema inputSchema = new InputNRRidPTidPIidPISchema();
+               inputSchema.setInput(createInstanceInput);
+               a1ControllerApi.a1ControllerCreatePolicyInstance(inputSchema);
+       }
+
+       @ApiOperation(value = "Deletes the policy instances for the given policy type.")
+       @DeleteMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{"
+                       + POLICY_INSTANCE_ID_NAME + "}")
+       @Secured({ DashboardConstants.ROLE_ADMIN })
+       public void deletePolicyInstance(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString,
+                       @PathVariable(POLICY_INSTANCE_ID_NAME) String policyInstanceId) {
+               logger.debug("deletePolicyInstance typeId: {}, instanceId: {}", policyTypeIdString, policyInstanceId);
+               InputNRRidPTidPIidSchemaInput instanceIdInput = new InputNRRidPTidPIidSchemaInput();
+               instanceIdInput.setNearRtRicId(NEAR_RT_RIC_ID);
+               instanceIdInput.setPolicyTypeId(Integer.decode(policyTypeIdString));
+               instanceIdInput.setPolicyInstanceId(policyInstanceId);
+               InputNRRidPTidPIidSchema inputSchema = new InputNRRidPTidPIidSchema();
+               inputSchema.setInput(instanceIdInput);
+               a1ControllerApi.a1ControllerDeletePolicyInstance(inputSchema);
+       }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/CustomResponseEntityExceptionHandler.java
new file mode 100644 (file)
index 0000000..f5ecd10
--- /dev/null
@@ -0,0 +1,82 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.model.ErrorTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestClientResponseException;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+/**
+ * Catches certain exceptions. This controller advice factors out try-catch
+ * blocks in many controller methods.
+ * 
+ * Also see:<br>
+ * https://www.baeldung.com/exception-handling-for-rest-with-spring
+ * https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services
+ */
+@ControllerAdvice
+public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
+
+       // Superclass has "logger" that is exposed here, so use a different name
+       private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       /**
+        * Logs the error and generates a JSON response when a REST controller method
+        * takes a RestClientResponseException. This is thrown by the Http client when a
+        * remote method returns a non-2xx code. All the controller methods are proxies
+        * in that they just forward the request along to a remote system, so if that
+        * remote system fails, return 502 plus some details about the failure, rather
+        * than the generic 500 that Spring-Boot will return on an uncaught exception.
+        * 
+        * Why 502? I quote: <blockquote>HTTP server received an invalid response from a
+        * server it consulted when acting as a proxy or gateway.</blockquote>
+        * 
+        * @param ex
+        *                    The exception
+        * 
+        * @param request
+        *                    The original request
+        * 
+        * @return A response entity with status code 502 plus some details in the body.
+        */
+       @ExceptionHandler({ RestClientResponseException.class })
+       public final ResponseEntity<ErrorTransport> handleProxyMethodException(Exception ex, WebRequest request) {
+               // Capture the full stack trace in the log.
+               log.error("handleProxyMethodException: request {}, exception {}", request.getDescription(false), ex);
+               if (ex instanceof HttpStatusCodeException) {
+                       HttpStatusCodeException hsce = (HttpStatusCodeException) ex;
+                       return new ResponseEntity<>(new ErrorTransport(hsce.getRawStatusCode(), hsce.getResponseBodyAsString(),
+                                       ex.toString(), request.getDescription(false)), HttpStatus.BAD_GATEWAY);
+               } else {
+                       return new ResponseEntity<>(new ErrorTransport(500, ex), HttpStatus.BAD_GATEWAY);
+               }
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/Html5PathsController.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/Html5PathsController.java
new file mode 100644 (file)
index 0000000..ed2e905
--- /dev/null
@@ -0,0 +1,67 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Listens for requests to known Angular routes.
+ */
+@Controller
+public class Html5PathsController {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       /**
+        * Forwards the browser to the index (main) page upon request of a known route.
+        * This unfortunately requires duplication of the Angular route strings in the
+        * path mappings on this method. Could switch to a regex pattern instead someday
+        * if the routes change too often.
+        * 
+        * https://stackoverflow.com/questions/44692781/configure-spring-boot-to-redirect-404-to-a-single-page-app
+        * 
+        * @param request
+        *                     HttpServletRequest
+        * @param response
+        *                     HttpServletResponse
+        * @throws IOException
+        *                         On error
+        */
+       @RequestMapping(method = { RequestMethod.OPTIONS, RequestMethod.GET }, //
+                       path = { "/catalog", "/control", "/stats", "/user" })
+       public void forwardAngularRoutes(HttpServletRequest request, HttpServletResponse response) throws IOException {
+               URL url = new URL(request.getScheme(), request.getServerName(), request.getServerPort(), "/index.html");
+               if (logger.isDebugEnabled())
+                       logger.debug("forwardAngularRoutes: {} redirected to {}", request.getRequestURI(), url);
+               response.sendRedirect(url.toString());
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/SimpleErrorController.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/controller/SimpleErrorController.java
new file mode 100644 (file)
index 0000000..e2248e6
--- /dev/null
@@ -0,0 +1,93 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * Provides a controller which is invoked on any error within the Spring-managed
+ * context, including page not found, and redirects the caller to a custom error
+ * page. The caller is also redirected to this page if a REST controller takes
+ * an uncaught exception.
+ * 
+ * If trace is requested via request parameter ("?trace=true") and available,
+ * adds stack trace information to the standard JSON error response.
+ * 
+ * Excluded from Swagger API documentation.
+ * 
+ * https://stackoverflow.com/questions/25356781/spring-boot-remove-whitelabel-error-page
+ * https://www.baeldung.com/spring-boot-custom-error-page
+ */
+
+@ApiIgnore
+@Controller
+@RequestMapping(value = SimpleErrorController.ERROR_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
+public class SimpleErrorController implements ErrorController {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       public static final String ERROR_PATH = "/error";
+
+       private final ErrorAttributes errorAttributes;
+
+       @Autowired
+       public SimpleErrorController(ErrorAttributes errorAttributes) {
+               this.errorAttributes = errorAttributes;
+       }
+
+       @Override
+       public String getErrorPath() {
+               logger.warn("getErrorPath");
+               return ERROR_PATH;
+       }
+
+       @GetMapping
+       public String handleError(HttpServletRequest request) {
+               ServletWebRequest servletWebRequest = new ServletWebRequest(request);
+               Throwable t = errorAttributes.getError(servletWebRequest);
+               if (t != null)
+                       logger.warn("handleError", t);
+               Map<String, Object> attributes = errorAttributes.getErrorAttributes(servletWebRequest, true);
+               attributes.forEach((attribute, value) -> {
+                       logger.warn("handleError: {} -> {}", attribute, value);
+               });
+               // Return the name of the page INCLUDING suffix, which I guess is a "view" name.
+               // Just "error" is not enough, but don't seem to need a ModelAndView object.
+               return "error.html";
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/CaasIngressDemo.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/CaasIngressDemo.java
new file mode 100644 (file)
index 0000000..3a75dfa
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.k8sapi;
+
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.util.HttpsURLConnectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+public class CaasIngressDemo {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       public static void main(String[] args) throws Exception {
+               HttpsURLConnectionUtils.turnOffSslChecking();
+               // Get IP address from REC deployment team for testing
+               final String podsUrl = "https://10.0.0.1:16443/api/v1/namespaces/ricaux/pods";
+               RestTemplate rt = new RestTemplate();
+               ResponseEntity<String> podsResponse = rt.getForEntity(podsUrl, String.class);
+               logger.info(podsResponse.getBody());
+               HttpsURLConnectionUtils.turnOnSslChecking();
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/SimpleKubernetesClient.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/k8sapi/SimpleKubernetesClient.java
new file mode 100644 (file)
index 0000000..bcfae62
--- /dev/null
@@ -0,0 +1,56 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.k8sapi;
+
+import java.lang.invoke.MethodHandles;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+
+/**
+ * Provides a method to get a list of pods using RestTemplate. As currently
+ * configured this uses the default JVM HTTPS support which can be configured to
+ * ignore errors as a workaround for self-signed SSL certificates.
+ */
+public class SimpleKubernetesClient {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       private final String k8sUrl;
+
+       public SimpleKubernetesClient(String baseUrl) {
+               logger.debug("ctor: baseUrl {}", baseUrl);
+               k8sUrl = baseUrl;
+       }
+
+       public String listPods(String namespace) {
+               logger.debug("listPods for namespace {}", namespace);
+               String podsUrl = new DefaultUriBuilderFactory(k8sUrl.trim()).builder().pathSegment("v1")
+                               .pathSegment("namespaces").pathSegment(namespace.trim()).pathSegment("pods").build().normalize()
+                               .toString();
+               RestTemplate rt = new RestTemplate();
+               ResponseEntity<String> podsResponse = rt.getForEntity(podsUrl, String.class);
+               return podsResponse.getBody();
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/EcompUserDetails.java
new file mode 100644 (file)
index 0000000..7bc9f00
--- /dev/null
@@ -0,0 +1,85 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public class EcompUserDetails implements UserDetails {
+
+       private static final long serialVersionUID = 1L;
+       private final EcompUser ecompUser;
+
+       // This is the default Spring role-name prefix.
+       private static final String ROLEP = "ROLE_";
+
+       public EcompUserDetails(EcompUser ecompUser) {
+               this.ecompUser = ecompUser;
+       }
+
+       /*
+        * Gets a list of authorities (roles) for this user. To keep Spring happy, every
+        * item has prefix ROLE_.
+        */
+       public Collection<? extends GrantedAuthority> getAuthorities() {
+               List<GrantedAuthority> roleList = new ArrayList<>();
+               Iterator<EcompRole> roleIter = ecompUser.getRoles().iterator();
+               while (roleIter.hasNext()) {
+                       EcompRole role = roleIter.next();
+                       // Add the prefix if the ONAP portal doesn't supply it.
+                       final String roleName = role.getName().startsWith(ROLEP) ? role.getName() : ROLEP + role.getName();
+                       roleList.add(new SimpleGrantedAuthority(roleName));
+               }
+               return roleList;
+       }
+
+       public String getPassword() {
+               return null;
+       }
+
+       public String getUsername() {
+               return ecompUser.getLoginId();
+       }
+
+       public boolean isAccountNonExpired() {
+               return true;
+       }
+
+       public boolean isAccountNonLocked() {
+               return true;
+       }
+
+       public boolean isCredentialsNonExpired() {
+               return true;
+       }
+
+       public boolean isEnabled() {
+               return ecompUser.isActive();
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/ErrorTransport.java
new file mode 100644 (file)
index 0000000..516e3c8
--- /dev/null
@@ -0,0 +1,133 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.ric.portal.dashboard.model;
+
+import java.time.Instant;
+
+/**
+ * This mimics the model Spring-Boot uses for a message returned on failure, to
+ * be serialized as JSON.
+ */
+public class ErrorTransport implements IDashboardResponse {
+
+       private Instant timestamp;
+       private Integer status;
+       private String error;
+       private String message;
+       private String path;
+
+       /**
+        * Builds an empty object.
+        */
+       public ErrorTransport() {
+               // no-arg constructor
+       }
+
+       /**
+        * Convenience constructor for minimal value set.
+        * 
+        * @param status
+        *                   Integer value like 400
+        * @param error
+        *                   Error message
+        */
+       public ErrorTransport(int status, String error) {
+               this(status, error, null, null);
+       }
+
+       /**
+        * Convenience constructor for populating an error from an exception
+        * 
+        * @param status
+        *                      Integer value like 400
+        * @param throwable
+        *                      The caught exception/throwable to convert to String with
+        *                      an upper bound on characters
+        */
+       public ErrorTransport(int status, Throwable throwable) {
+               this.timestamp = Instant.now();
+               this.status = status;
+               final int enough = 256;
+               String exString = throwable.toString();
+               this.error = exString.length() > enough ? exString.substring(0, enough) : exString;
+       }
+
+       /**
+        * Builds an object with all fields
+        * 
+        * @param status
+        *                    Integer value like 500
+        * @param error
+        *                    Explanation
+        * @param message
+        *                    Additional explanation
+        * @param path
+        *                    Requested path
+        */
+       public ErrorTransport(int status, String error, String message, String path) {
+               this.timestamp = Instant.now();
+               this.status = status;
+               this.error = error;
+               this.message = message;
+               this.path = path;
+       }
+
+       public Integer getStatus() {
+               return status;
+       }
+
+       public void setStatus(Integer status) {
+               this.status = status;
+       }
+
+       public String getMessage() {
+               return message;
+       }
+
+       public void setMessage(String error) {
+               this.message = error;
+       }
+
+       public Instant getTimestamp() {
+               return timestamp;
+       }
+
+       public void setTimestamp(Instant timestamp) {
+               this.timestamp = timestamp;
+       }
+
+       public String getError() {
+               return error;
+       }
+
+       public void setError(String error) {
+               this.error = error;
+       }
+
+       public String getPath() {
+               return path;
+       }
+
+       public void setPath(String path) {
+               this.path = path;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/IDashboardResponse.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/IDashboardResponse.java
new file mode 100644 (file)
index 0000000..a298fa6
--- /dev/null
@@ -0,0 +1,27 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+/**
+ * Marker interface used by all transport models.
+ */
+public interface IDashboardResponse {
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstance.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstance.java
new file mode 100644 (file)
index 0000000..b699b3c
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+public class PolicyInstance implements IDashboardResponse {
+       private String instanceId;
+       private Object instance;
+
+       public PolicyInstance(String id, Object instance) {
+               this.instanceId = id;
+               this.instance = instance;
+       }
+
+       public String getInstanceId() {
+               return instanceId;
+       }
+       public void setInstanceId(String instanceId) {
+               this.instanceId = instanceId;
+       }
+       public Object getInstance() {
+               return instance;
+       }
+       public void setInstance(Object instance) {
+               this.instance = instance;
+       }
+
+       @Override
+       public String toString() {
+               return PolicyInstance.class.getName() + ": [id:" + instanceId + ", instance: " + instance + "]";
+       }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstances.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyInstances.java
new file mode 100644 (file)
index 0000000..c750487
--- /dev/null
@@ -0,0 +1,28 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+import java.util.ArrayList;
+
+public class PolicyInstances extends ArrayList<PolicyInstance> {
+
+       private static final long serialVersionUID = -928428052502491021L;
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyType.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyType.java
new file mode 100644 (file)
index 0000000..d24667a
--- /dev/null
@@ -0,0 +1,81 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PolicyType {
+
+    @JsonProperty("policy_type_id")
+       Integer policyTypeId;
+
+       @JsonProperty("name")
+       String name;
+
+       @JsonProperty("description")
+       String description;
+
+       @JsonProperty("create_schema")
+       String createSchema;
+
+       public PolicyType(Integer policyId, String name, String description, String createSchema) {
+               this.policyTypeId = policyId;
+               this.name = name;
+               this.description = description;
+               this.createSchema = createSchema;
+       }
+
+       public Integer getPolicyTypeId() {
+               return policyTypeId;
+       }
+
+       public void setPolicyTypeId(Integer policyTypeId) {
+               this.policyTypeId = policyTypeId;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public String getDescription() {
+               return description;
+       }
+
+       public void setDescription(String description) {
+               this.description = description;
+       }
+
+       public String getCreateSchema() {
+               return createSchema;
+       }
+
+       public void setCreateSchema(String createSchema) {
+               this.createSchema = createSchema;
+       }
+
+       @Override
+       public String toString() {
+               return "[policy_type_id:" + policyTypeId +  ", name:" + name + ", description:" + description + ", create_schema:" + createSchema + "]";
+       }
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyTypes.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/PolicyTypes.java
new file mode 100644 (file)
index 0000000..3165d1b
--- /dev/null
@@ -0,0 +1,28 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+import java.util.ArrayList;
+
+public class PolicyTypes extends ArrayList<PolicyType> {
+
+       private static final long serialVersionUID = -928428052502491021L;
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/model/SuccessTransport.java
new file mode 100644 (file)
index 0000000..9e13789
--- /dev/null
@@ -0,0 +1,63 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.model;
+
+public class SuccessTransport implements IDashboardResponse {
+
+       private int status;
+       private Object data;
+
+       /**
+        * Builds an empty object
+        */
+       public SuccessTransport() {
+               // no-arg constructor
+       }
+
+       /**
+        * Builds an object with the specified values.
+        * 
+        * @param status
+        *                   Status code
+        * @param data
+        *                   Data to transport
+        */
+       public SuccessTransport(int status, Object data) {
+               this.status = status;
+               this.data = data;
+       }
+
+       public int getStatus() {
+               return status;
+       }
+
+       public void setStatus(int status) {
+               this.status = status;
+       }
+
+       public Object getData() {
+               return data;
+       }
+
+       public void setData(Object data) {
+               this.data = data;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/IPortalSdkDecryptor.java
new file mode 100644 (file)
index 0000000..34d80c9
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+
+/**
+ * Supports an upgrade path among methods in CipherUtil because the PortalSDK is
+ * changing encryption methods.
+ */
+public interface IPortalSdkDecryptor {
+
+       /**
+        * Decrypts the specified value using a known key.
+        * 
+        * @param cipherText
+        *                       Encrypted value
+        * @return Clear text on success, null otherwise.
+        * @throws CipherUtilException
+        *                                 if any decryption step fails
+        */
+       String decrypt(String cipherText) throws CipherUtilException;
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthManager.java
new file mode 100644 (file)
index 0000000..dc70f7e
--- /dev/null
@@ -0,0 +1,120 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.IPortalRestCentralService;
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides services to authenticate requests from/to ONAP Portal.
+ */
+public class PortalAuthManager {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       final Map<String, String> credentialsMap;
+       private final IPortalSdkDecryptor portalSdkDecryptor;
+       private final String userIdCookieName;
+
+       public PortalAuthManager(final String appName, final String username, final String password,
+                       final String decryptorClassName, final String userCookie)
+                       throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException,
+                       InvocationTargetException, NoSuchMethodException, SecurityException {
+               credentialsMap = new HashMap<>();
+               credentialsMap.put(IPortalRestCentralService.CREDENTIALS_APP, appName);
+               credentialsMap.put(IPortalRestCentralService.CREDENTIALS_USER, username);
+               credentialsMap.put(IPortalRestCentralService.CREDENTIALS_PASS, password);
+               this.userIdCookieName = userCookie;
+               // Instantiate here so configuration errors are detected at app-start time
+               logger.debug("ctor: using decryptor class {}", decryptorClassName);
+               Class<?> decryptorClass = Class.forName(decryptorClassName);
+               portalSdkDecryptor = (IPortalSdkDecryptor) decryptorClass.getDeclaredConstructor().newInstance();
+       }
+
+       /**
+        * @return A map of key-value pairs with application name, user name and
+        *         password.
+        */
+       public Map<String, String> getAppCredentials() {
+               return credentialsMap;
+       }
+
+       /**
+        * Searches the request for a cookie with the specified name.
+        *
+        * @param request
+        *                       HttpServletRequest
+        * @param cookieName
+        *                       Cookie name
+        * @return Cookie, or null if not found.
+        */
+       private Cookie getCookie(HttpServletRequest request, String cookieName) {
+               Cookie[] cookies = request.getCookies();
+               if (cookies != null)
+                       for (Cookie cookie : cookies)
+                               if (cookie.getName().equals(cookieName))
+                                       return cookie;
+               return null;
+       }
+
+       /**
+        * Validates whether the ECOMP Portal sign-on process has completed. Checks for
+        * the ECOMP cookie first, then the user cookie.
+        * 
+        * @param request
+        *                    HttpServletRequest
+        * @return User ID if the ECOMP cookie is present and the sign-on process
+        *         established a user ID; else null.
+        */
+       public String validateEcompSso(HttpServletRequest request) {
+               // Check ECOMP Portal cookie
+               Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+               if (ep == null) {
+                       logger.debug("validateEcompSso: cookie not found: {}", PortalApiConstants.EP_SERVICE);
+                       return null;
+               }
+               logger.trace("validateEcompSso: found cookie {}", PortalApiConstants.EP_SERVICE);
+               Cookie user = getCookie(request, userIdCookieName);
+               if (user == null) {
+                       logger.debug("validateEcompSso: cookie not found: {}", userIdCookieName);
+                       return null;
+               }
+               logger.trace("validateEcompSso: user cookie {}", userIdCookieName);
+               String userid = null;
+               try {
+                       userid = portalSdkDecryptor.decrypt(user.getValue());
+               } catch (CipherUtilException e) {
+                       throw new IllegalArgumentException("validateEcompSso failed", e);
+               }
+               return userid;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalAuthenticationFilter.java
new file mode 100644 (file)
index 0000000..4b6de91
--- /dev/null
@@ -0,0 +1,281 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.invoke.MethodHandles;
+import java.net.URLEncoder;
+import java.util.HashSet;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.onap.portalsdk.core.onboarding.util.KeyProperties;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.onboarding.util.PortalApiProperties;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.model.EcompUserDetails;
+import org.owasp.esapi.reference.DefaultSecurityConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+/**
+ * This filter checks every request for the cookie set by the ONAP Portal single
+ * sign on process. The possible paths and actions:
+ * <OL>
+ * <LI>User starts at an app page via a bookmark. No Portal cookie is set.
+ * Redirect there to get one; then continue as below.
+ * <LI>User starts at Portal and goes to app. Alternately, the user's session
+ * times out and the user hits refresh. The Portal cookie is set, but there is
+ * no valid session. Create one and publish info.
+ * <LI>User has valid Portal cookie and session. Reset the max idle in that
+ * session.
+ * </OL>
+ * <P>
+ * Notes:
+ * <UL>
+ * <LI>While redirecting, the cookie "redirectUrl" should also be set so that
+ * Portal knows where to forward the request to once the Portal Session is
+ * created and EPService cookie is set.
+ * </UL>
+ * 
+ * TODO: What about sessions? Will this be stateless?
+ * 
+ * This filter uses no annotations to avoid Spring's automatic registration,
+ * which add this filter in the chain in the wrong order.
+ */
+public class PortalAuthenticationFilter implements Filter {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // Unfortunately not all file names are defined as constants
+       private static final String[] securityPropertyFiles = { KeyProperties.PROPERTY_FILE_NAME,
+                       PortalApiProperties.PROPERTY_FILE_NAME, DefaultSecurityConfiguration.DEFAULT_RESOURCE_FILE,
+                       "validation.properties" };
+
+       public static final String REDIRECT_URL_KEY = "redirectUrl";
+
+       private final boolean enforcePortalSecurity;
+       private final PortalAuthManager authManager;
+
+       private final DashboardUserManager userManager;
+
+       public PortalAuthenticationFilter(boolean portalSecurity, PortalAuthManager authManager,
+                       DashboardUserManager userManager) {
+               this.enforcePortalSecurity = portalSecurity;
+               this.authManager = authManager;
+               this.userManager = userManager;
+               if (portalSecurity) {
+                       // Throw if security is requested and prerequisites are not met
+                       for (String pf : securityPropertyFiles) {
+                               InputStream in = MethodHandles.lookup().lookupClass().getClassLoader().getResourceAsStream(pf);
+                               if (in == null) {
+                                       String msg = "Failed to find property file on classpath: " + pf;
+                                       logger.error(msg);
+                                       throw new RuntimeException(msg);
+                               } else {
+                                       try {
+                                               in.close();
+                                       } catch (IOException ex) {
+                                               logger.warn("Failed to close stream", ex);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       @Override
+       public void init(FilterConfig filterConfig) throws ServletException {
+               // complain loudly if this key property is missing
+               String url = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+               logger.debug("init: Portal redirect URL {}", url);
+               if (url == null)
+                       logger.error(
+                                       "init: Failed to find property in portal.properties: " + PortalApiConstants.ECOMP_REDIRECT_URL);
+       }
+
+       @Override
+       public void destroy() {
+               // No resources to release
+       }
+
+       /**
+        * Requests for pages ignored in the web security config do not hit this filter.
+        */
+       @Override
+       public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+                       throws IOException, ServletException {
+               if (enforcePortalSecurity)
+                       doFilterEPSDKFW(req, res, chain);
+               else
+                       doFilterMockUserAdminRole(req, res, chain);
+       }
+
+       /*
+        * Populates security context with a mock user in the admin role.
+        * 
+        */
+       private void doFilterMockUserAdminRole(ServletRequest req, ServletResponse res, FilterChain chain)
+                       throws IOException, ServletException {
+               Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+               if (auth == null || auth.getAuthorities().isEmpty()) {
+                       if (logger.isDebugEnabled()) {
+                               logger.debug("doFilter adding auth to request URI {}",
+                                               (req instanceof HttpServletRequest) ? ((HttpServletRequest) req).getRequestURL() : req);
+                       }
+                       EcompRole admin = new EcompRole();
+                       admin.setId(1L);
+                       admin.setName(DashboardConstants.ROLE_ADMIN);
+                       HashSet<EcompRole> roles = new HashSet<>();
+                       roles.add(admin);
+                       EcompUser user = new EcompUser();
+                       user.setLoginId("fakeLoginId");
+                       user.setRoles(roles);
+                       user.setActive(true);
+                       EcompUserDetails userDetails = new EcompUserDetails(user);
+                       PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
+                                       "fakeCredentials", userDetails.getAuthorities());
+                       SecurityContextHolder.getContext().setAuthentication(authToken);
+               } else {
+                       logger.debug("doFilter: authorities {}", auth.getAuthorities());
+               }
+               chain.doFilter(req, res);
+       }
+
+       /*
+        * Checks for valid cookies and allows request to be served if found; redirects
+        * to Portal otherwise.
+        */
+       private void doFilterEPSDKFW(ServletRequest req, ServletResponse res, FilterChain chain)
+                       throws IOException, ServletException {
+               HttpServletRequest request = (HttpServletRequest) req;
+               HttpServletResponse response = (HttpServletResponse) res;
+               if (logger.isTraceEnabled())
+                       logger.trace("doFilter: req {}", request.getRequestURI());
+               // Need to authenticate the request
+               final String userId = authManager.validateEcompSso(request);
+               final EcompUser ecompUser = (userId == null ? null : userManager.getUser(userId));
+               if (userId == null || ecompUser == null) {
+                       logger.debug("doFilter: unauthorized user requests URI {}, serving login page", request.getRequestURI());
+                       StringBuffer sb = request.getRequestURL();
+                       sb.append(request.getQueryString() == null ? "" : "?" + request.getQueryString());
+                       String body = generateLoginRedirectPage(sb.toString());
+                       response.setContentType(MediaType.TEXT_HTML_VALUE);
+                       response.getWriter().print(body);
+                       response.getWriter().flush();
+               } else {
+                       EcompUserDetails userDetails = new EcompUserDetails(ecompUser);
+                       // Using portal session as credentials is a hack
+                       PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(userDetails,
+                                       getPortalSessionId(request), userDetails.getAuthorities());
+                       SecurityContextHolder.getContext().setAuthentication(authToken);
+                       // Pass request back down the filter chain
+                       chain.doFilter(request, response);
+               }
+       }
+
+       /**
+        * Generates a page with text only, absolutely no references to any webapp
+        * resources, so this can be served to an unauthenticated user without
+        * triggering a new authentication attempt. The page has a link to the Portal
+        * URL from configuration, with a return URL that is the original request.
+        * 
+        * @param appUrl
+        *                   Original requested URL
+        * @return HTML
+        * @throws UnsupportedEncodingException
+        *                                          On error
+        */
+       private static String generateLoginRedirectPage(String appUrl) throws UnsupportedEncodingException {
+               String encodedAppUrl = URLEncoder.encode(appUrl, "UTF-8");
+               String portalBaseUrl = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL);
+               String redirectUrl = portalBaseUrl + "?" + PortalAuthenticationFilter.REDIRECT_URL_KEY + "=" + encodedAppUrl;
+               String aHref = "<a href=\"" + redirectUrl + "\">";
+               // If only Java had "here" documents.
+               String body = String.join(//
+                               System.getProperty("line.separator"), //
+                               "<html>", //
+                               "<head>", //
+                               "<title>RIC Dashboard</title>", //
+                               "<style>", //
+                               "html, body { ", //
+                               "  font-family: Helvetica, Arial, sans-serif;", //
+                               "}", //
+                               "</style>", //
+                               "</head>", //
+                               "<body>", //
+                               "<h2>RIC Dashboard</h2>", //
+                               "<h4>Please log in.</h4>", //
+                               "<p>", //
+                               aHref, "Click here to authenticate at the ONAP Portal</a>", //
+                               "</p>", //
+                               "</body>", //
+                               "</html>");
+               return body;
+       }
+
+       /**
+        * Searches the request for a cookie with the specified name.
+        *
+        * @param request
+        *                       HttpServletRequest
+        * @param cookieName
+        *                       Cookie name
+        * @return Cookie, or null if not found.
+        */
+       private Cookie getCookie(HttpServletRequest request, String cookieName) {
+               Cookie[] cookies = request.getCookies();
+               if (cookies != null)
+                       for (Cookie cookie : cookies)
+                               if (cookie.getName().equals(cookieName))
+                                       return cookie;
+               return null;
+       }
+
+       /**
+        * Gets the ECOMP Portal service cookie value.
+        * 
+        * @param request
+        * @return Cookie value, or null if not found.
+        */
+       private String getPortalSessionId(HttpServletRequest request) {
+               Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+               if (ep == null)
+                       return null;
+               return ep.getValue();
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalRestCentralServiceImpl.java
new file mode 100644 (file)
index 0000000..581ca25
--- /dev/null
@@ -0,0 +1,89 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.IPortalRestCentralService;
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardUserManager;
+import org.oransc.ric.portal.dashboard.config.SpringContextCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Implements the contract used by the Portal to transmit user details to this
+ * on-boarded application. The requests are intercepted first by a servlet in
+ * the EPSDK-FW library, which proxies the calls to these methods.
+ * 
+ * An instance of this class is created upon first request to the API. But this
+ * class is found and instantiated via Class.forName(), so cannot use Spring
+ * annotations.
+ */
+public class PortalRestCentralServiceImpl implements IPortalRestCentralService {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       private final PortalAuthManager authManager;
+       private final DashboardUserManager userManager;
+
+       public PortalRestCentralServiceImpl() throws IOException, PortalAPIException {
+               final ApplicationContext context = SpringContextCache.getApplicationContext();
+               authManager = (PortalAuthManager) context.getBean(PortalAuthManager.class);
+               userManager = (DashboardUserManager) context.getBean(DashboardUserManager.class);
+       }
+
+       /*
+        * Answers the Portal API credentials.
+        */
+       @Override
+       public Map<String, String> getAppCredentials() throws PortalAPIException {
+               logger.debug("getAppCredentials");
+               return authManager.getAppCredentials();
+       }
+
+       /*
+        * Extracts the user ID from a cookie in the header
+        */
+       @Override
+       public String getUserId(HttpServletRequest request) throws PortalAPIException {
+               logger.debug("getuserId");
+               return authManager.validateEcompSso(request);
+       }
+
+       @Override
+       public void pushUser(EcompUser user) throws PortalAPIException {
+               logger.debug("pushUser: {}", user);
+               userManager.createUser(user);
+       }
+
+       @Override
+       public void editUser(String loginId, EcompUser user) throws PortalAPIException {
+               logger.debug("editUser: {}", user);
+               userManager.updateUser(loginId, user);
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorAes.java
new file mode 100644 (file)
index 0000000..279f1dd
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorAes implements IPortalSdkDecryptor {
+
+       @SuppressWarnings("deprecation")
+       public String decrypt(String cipherText) throws CipherUtilException {
+               return CipherUtil.decrypt(cipherText);
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/portalapi/PortalSdkDecryptorPkc.java
new file mode 100644 (file)
index 0000000..0127267
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorPkc implements IPortalSdkDecryptor {
+
+       public String decrypt(String cipherText) throws CipherUtilException {
+               return CipherUtil.decryptPKC(cipherText);
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/util/HttpsURLConnectionUtils.java b/dashboard/webapp-backend/src/main/java/org/oransc/ric/portal/dashboard/util/HttpsURLConnectionUtils.java
new file mode 100644 (file)
index 0000000..a97ed7b
--- /dev/null
@@ -0,0 +1,82 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.ric.portal.dashboard.util;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Disables and enables certificate and host-name checking in
+ * HttpsURLConnection, the default JVM implementation of the HTTPS/TLS protocol.
+ * Has no effect on implementations such as Apache Http Client, Ok Http.
+ * 
+ * https://stackoverflow.com/questions/23504819/how-to-disable-ssl-certificate-checking-with-spring-resttemplate/58291331#58291331
+ */
+public final class HttpsURLConnectionUtils {
+
+       private static final HostnameVerifier jvmHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+
+       private static final HostnameVerifier trivialHostnameVerifier = new HostnameVerifier() {
+               public boolean verify(String hostname, SSLSession sslSession) {
+                       return true;
+               }
+       };
+
+       private static final TrustManager[] UNQUESTIONING_TRUST_MANAGER = new TrustManager[] { new X509TrustManager() {
+               public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+                       return null;
+               }
+
+               public void checkClientTrusted(X509Certificate[] certs, String authType) {
+               }
+
+               public void checkServerTrusted(X509Certificate[] certs, String authType) {
+               }
+       } };
+
+       public static void turnOffSslChecking() throws NoSuchAlgorithmException, KeyManagementException {
+               HttpsURLConnection.setDefaultHostnameVerifier(trivialHostnameVerifier);
+               // Install the all-trusting trust manager
+               SSLContext sc = SSLContext.getInstance("SSL");
+               sc.init(null, UNQUESTIONING_TRUST_MANAGER, null);
+               HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+       }
+
+       public static void turnOnSslChecking() throws KeyManagementException, NoSuchAlgorithmException {
+               HttpsURLConnection.setDefaultHostnameVerifier(jvmHostnameVerifier);
+               // Return it to the initial state (discovered by reflection, now hardcoded)
+               SSLContext sc = SSLContext.getInstance("SSL");
+               sc.init(null, null, null);
+               HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+       }
+
+       private HttpsURLConnectionUtils() {
+               throw new UnsupportedOperationException("Do not instantiate libraries.");
+       }
+}
diff --git a/dashboard/webapp-backend/src/main/resources/ESAPI.properties b/dashboard/webapp-backend/src/main/resources/ESAPI.properties
new file mode 100644 (file)
index 0000000..9a26119
--- /dev/null
@@ -0,0 +1,385 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+#===========================================================================
+# ESAPI Configuration
+#
+# If true, then print all the ESAPI properties set here when they are loaded.
+# If false, they are not printed. Useful to reduce output when running JUnit tests.
+# If you need to troubleshoot a properties related problem, turning this on may help.
+# This is 'false' in the src/test/resources/.esapi version. It is 'true' by
+# default for reasons of backward compatibility with earlier ESAPI versions.
+ESAPI.printProperties=false
+
+# ESAPI is designed to be easily extensible. You can use the reference implementation
+# or implement your own providers to take advantage of your enterprise's security
+# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like:
+#
+#    String ciphertext =
+#              ESAPI.encryptor().encrypt("Secret message");   // Deprecated in 2.0
+#    CipherText cipherText =
+#              ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred
+#
+# Below you can specify the classname for the provider that you wish to use in your
+# application. The only requirement is that it implement the appropriate ESAPI interface.
+# This allows you to switch security implementations in the future without rewriting the
+# entire application.
+#
+# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory
+ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController
+# FileBasedAuthenticator requires users.txt file in .esapi directory
+ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator
+ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
+ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor
+
+ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor
+ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities
+ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector
+#ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory
+ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer
+ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator
+
+#===========================================================================
+# ESAPI Authenticator
+#
+Authenticator.AllowedLoginAttempts=3
+#Authenticator.MaxOldPasswordHashes=13
+Authenticator.UsernameParameterName=username
+#Authenticator.PasswordParameterName=password
+# RememberTokenDuration (in days)
+Authenticator.RememberTokenDuration=14
+# Session Timeouts (in minutes)
+Authenticator.IdleTimeoutDuration=20
+Authenticator.AbsoluteTimeoutDuration=120
+
+#===========================================================================
+# ESAPI Encoder
+#
+# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks.
+# Failure to canonicalize input is a very common mistake when implementing validation schemes.
+# Canonicalization is automatic when using the ESAPI Validator, but you can also use the
+# following code to canonicalize data.
+#
+#      ESAPI.Encoder().canonicalize( "%22hello world&#x22;" );
+#  
+# Multiple encoding is when a single encoding format is applied multiple times. Allowing
+# multiple encoding is strongly discouraged.
+Encoder.AllowMultipleEncoding=false
+
+# Mixed encoding is when multiple different encoding formats are applied, or when 
+# multiple formats are nested. Allowing multiple encoding is strongly discouraged.
+Encoder.AllowMixedEncoding=false
+
+# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs
+# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or
+# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important.
+Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec
+
+
+#===========================================================================
+# ESAPI Encryption
+#
+# The ESAPI Encryptor provides basic cryptographic functions with a simplified API.
+# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+# There is not currently any support for key rotation, so be careful when changing your key and salt as it
+# will invalidate all signed, encrypted, and hashed data.
+#
+# WARNING: Not all combinations of algorithms and key lengths are supported.
+# If you choose to use a key length greater than 128, you MUST download the
+# unlimited strength policy files and install in the lib directory of your JRE/JDK.
+# See http://java.sun.com/javase/downloads/index.jsp for more information.
+#
+# Backward compatibility with ESAPI Java 1.4 is supported by the two deprecated API
+# methods, Encryptor.encrypt(String) and Encryptor.decrypt(String). However, whenever
+# possible, these methods should be avoided as they use ECB cipher mode, which in almost
+# all circumstances a poor choice because of it's weakness. CBC cipher mode is the default
+# for the new Encryptor encrypt / decrypt methods for ESAPI Java 2.0.  In general, you
+# should only use this compatibility setting if you have persistent data encrypted with
+# version 1.4 and even then, you should ONLY set this compatibility mode UNTIL
+# you have decrypted all of your old encrypted data and then re-encrypted it with
+# ESAPI 2.0 using CBC mode. If you have some reason to mix the deprecated 1.4 mode
+# with the new 2.0 methods, make sure that you use the same cipher algorithm for both
+# (256-bit AES was the default for 1.4; 128-bit is the default for 2.0; see below for
+# more details.) Otherwise, you will have to use the new 2.0 encrypt / decrypt methods
+# where you can specify a SecretKey. (Note that if you are using the 256-bit AES,
+# that requires downloading the special jurisdiction policy files mentioned above.)
+#
+#              ***** IMPORTANT: Do NOT forget to replace these with your own values! *****
+# To calculate these values, you can run:
+#              java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+#
+Encryptor.MasterKey=tzfztf56ftv
+Encryptor.MasterSalt=123456ztrewq
+
+# Provides the default JCE provider that ESAPI will "prefer" for its symmetric
+# encryption and hashing. (That is it will look to this provider first, but it
+# will defer to other providers if the requested algorithm is not implemented
+# by this provider.) If left unset, ESAPI will just use your Java VM's current
+# preferred JCE provider, which is generally set in the file
+# "$JAVA_HOME/jre/lib/security/java.security".
+#
+# The main intent of this is to allow ESAPI symmetric encryption to be
+# used with a FIPS 140-2 compliant crypto-module. For details, see the section
+# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in
+# the ESAPI 2.0 Symmetric Encryption User Guide, at:
+# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html
+# However, this property also allows you to easily use an alternate JCE provider
+# such as "Bouncy Castle" without having to make changes to "java.security".
+# See Javadoc for SecurityProviderLoader for further details. If you wish to use
+# a provider that is not known to SecurityProviderLoader, you may specify the
+# fully-qualified class name of the JCE provider class that implements
+# java.security.Provider. If the name contains a '.', this is interpreted as
+# a fully-qualified class name that implements java.security.Provider.
+#
+# NOTE: Setting this property has the side-effect of changing it in your application
+#       as well, so if you are using JCE in your application directly rather than
+#       through ESAPI (you wouldn't do that, would you? ;-), it will change the
+#       preferred JCE provider there as well.
+#
+# Default: Keeps the JCE provider set to whatever JVM sets it to.
+Encryptor.PreferredJCEProvider=
+
+# AES is the most widely used and strongest encryption algorithm. This
+# should agree with your Encryptor.CipherTransformation property.
+# By default, ESAPI Java 1.4 uses "PBEWithMD5AndDES" and which is
+# very weak. It is essentially a password-based encryption key, hashed
+# with MD5 around 1K times and then encrypted with the weak DES algorithm
+# (56-bits) using ECB mode and an unspecified padding (it is
+# JCE provider specific, but most likely "NoPadding"). However, 2.0 uses
+# "AES/CBC/PKCSPadding". If you want to change these, change them here.
+# Warning: This property does not control the default reference implementation for
+#                 ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped
+#                 in the future.
+# @deprecated
+Encryptor.EncryptionAlgorithm=AES
+#              For ESAPI Java 2.0 - New encrypt / decrypt methods use this.
+Encryptor.CipherTransformation=AES/CBC/PKCS5Padding
+
+# Applies to ESAPI 2.0 and later only!
+# Comma-separated list of cipher modes that provide *BOTH*
+# confidentiality *AND* message authenticity. (NIST refers to such cipher
+# modes as "combined modes" so that's what we shall call them.) If any of these
+# cipher modes are used then no MAC is calculated and stored
+# in the CipherText upon encryption. Likewise, if one of these
+# cipher modes is used with decryption, no attempt will be made
+# to validate the MAC contained in the CipherText object regardless
+# of whether it contains one or not. Since the expectation is that
+# these cipher modes support support message authenticity already,
+# injecting a MAC in the CipherText object would be at best redundant.
+#
+# Note that as of JDK 1.5, the SunJCE provider does not support *any*
+# of these cipher modes. Of these listed, only GCM and CCM are currently
+# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports
+# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other
+# padding modes.
+Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC
+
+# Applies to ESAPI 2.0 and later only!
+# Additional cipher modes allowed for ESAPI 2.0 encryption. These
+# cipher modes are in _addition_ to those specified by the property
+# 'Encryptor.cipher_modes.combined_modes'.
+# Note: We will add support for streaming modes like CFB & OFB once
+# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod'
+# (probably in ESAPI 2.1).
+# DISCUSS: Better name?
+Encryptor.cipher_modes.additional_allowed=CBC
+
+# 128-bit is almost always sufficient and appears to be more resistant to
+# related key attacks than is 256-bit AES. Use '_' to use default key size
+# for cipher algorithms (where it makes sense because the algorithm supports
+# a variable key size). Key length must agree to what's provided as the
+# cipher transformation, otherwise this will be ignored after logging a
+# warning.
+#
+# NOTE: This is what applies BOTH ESAPI 1.4 and 2.0. See warning above about mixing!
+Encryptor.EncryptionKeyLength=128
+
+# Because 2.0 uses CBC mode by default, it requires an initialization vector (IV).
+# (All cipher modes except ECB require an IV.) There are two choices: we can either
+# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While
+# the IV does not need to be hidden from adversaries, it is important that the
+# adversary not be allowed to choose it. Also, random IVs are generally much more
+# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes
+# such as CFB and OFB use a different IV for each encryption with a given key so
+# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random
+# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and
+# uncomment the Encryptor.fixedIV.
+#
+# Valid values:                random|fixed|specified          'specified' not yet implemented; planned for 2.1
+Encryptor.ChooseIVMethod=random
+# If you choose to use a fixed IV, then you must place a fixed IV here that
+# is known to all others who are sharing your secret key. The format should
+# be a hex string that is the same length as the cipher block size for the
+# cipher algorithm that you are using. The following is an *example* for AES
+# from an AES test vector for AES-128/CBC as described in:
+# NIST Special Publication 800-38A (2001 Edition)
+# "Recommendation for Block Cipher Modes of Operation".
+# (Note that the block size for AES is 16 bytes == 128 bits.)
+#
+Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f
+
+# Whether or not CipherText should use a message authentication code (MAC) with it.
+# This prevents an adversary from altering the IV as well as allowing a more
+# fool-proof way of determining the decryption failed because of an incorrect
+# key being supplied. This refers to the "separate" MAC calculated and stored
+# in CipherText, not part of any MAC that is calculated as a result of a
+# "combined mode" cipher mode.
+#
+# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also
+# set this property to false.
+Encryptor.CipherText.useMAC=true
+
+# Whether or not the PlainText object may be overwritten and then marked
+# eligible for garbage collection. If not set, this is still treated as 'true'.
+Encryptor.PlainText.overwrite=true
+
+# Do not use DES except in a legacy situations. 56-bit is way too small key size.
+#Encryptor.EncryptionKeyLength=56
+#Encryptor.EncryptionAlgorithm=DES
+
+# TripleDES is considered strong enough for most purposes.
+#      Note:   There is also a 112-bit version of DESede. Using the 168-bit version
+#                      requires downloading the special jurisdiction policy from Sun.
+#Encryptor.EncryptionKeyLength=168
+#Encryptor.EncryptionAlgorithm=DESede
+
+Encryptor.HashAlgorithm=SHA-512
+Encryptor.HashIterations=1024
+Encryptor.DigitalSignatureAlgorithm=SHA1withDSA
+Encryptor.DigitalSignatureKeyLength=1024
+Encryptor.RandomAlgorithm=SHA1PRNG
+Encryptor.CharacterEncoding=UTF-8
+
+# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function
+# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and
+# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for
+# the MAC, mostly to keep the overall size at a minimum.)
+#
+# Currently supported choices for JDK 1.5 and 1.6 are:
+#      HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and
+#      HmacSHA512 (512 bits).
+# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though
+# the JDKs support it.  See the ESAPI 2.0 Symmetric Encryption User Guide
+# further details.
+Encryptor.KDF.PRF=HmacSHA256
+#===========================================================================
+# ESAPI Logging
+# Set the application name if these logs are combined with other applications
+Logger.ApplicationName=portal_ric_dashboard
+# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true
+Logger.LogEncodingRequired=false
+# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments.
+Logger.LogApplicationName=true
+# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments.
+Logger.LogServerIP=true
+# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you
+# want to place it in a specific directory.
+Logger.LogFileName=portal_ric_dashboard_esapi_log
+# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000)
+Logger.MaxLogFileSize=10000000
+
+
+#===========================================================================
+# ESAPI Intrusion Detection
+#
+# Each event has a base to which .count, .interval, and .action are added
+# The IntrusionException will fire if we receive "count" events within "interval" seconds
+# The IntrusionDetector is configurable to take the following actions: log, logout, and disable
+#  (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable
+#
+# Custom Events
+# Names must start with "event." as the base
+# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here
+# You can also disable intrusion detection completely by changing
+# the following parameter to true
+#
+IntrusionDetector.Disable=false
+#
+IntrusionDetector.event.test.count=2
+IntrusionDetector.event.test.interval=10
+IntrusionDetector.event.test.actions=disable,log
+
+# Exception Events
+# All EnterpriseSecurityExceptions are registered automatically
+# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException
+# Use the fully qualified classname of the exception as the base
+
+# any intrusion is an attack
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout
+
+# for test purposes
+# CHECKME: Shouldn't there be something in the property name itself that designates
+#                 that these are for testing???
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout
+
+# rapid validation errors indicate scans or attacks in progress
+# org.owasp.esapi.errors.ValidationException.count=10
+# org.owasp.esapi.errors.ValidationException.interval=10
+# org.owasp.esapi.errors.ValidationException.actions=log,logout
+
+# sessions jumping between hosts indicates session hijacking
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout
+
+
+#===========================================================================
+# ESAPI Validation
+#
+# The ESAPI Validator works on regular expressions with defined names. You can define names
+# either here, or you may define application specific patterns in a separate file defined below.
+# This allows enterprises to specify both organizational standards as well as application specific
+# validation rules.
+#
+Validator.ConfigurationFile=validation.properties
+Validator.ConfigurationFile.MultiValued=false
+
+# Validators used by ESAPI
+Validator.AccountName=^[a-zA-Z0-9]{3,20}$
+Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$
+Validator.RoleName=^[a-z]{1,20}$
+
+#the word TEST below should be changed to your application 
+#name - only relative URL's are supported
+Validator.Redirect=^\\/test.*$
+
+# Global HTTP Validation Rules
+# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=]
+Validator.HTTPScheme=^(http|https)$
+Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$
+Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$
+Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$
+Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$
+Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$
+Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$
+Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPURL=^.*$
+Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$
+
+# Validation of file related input
+Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
+Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
diff --git a/dashboard/webapp-backend/src/main/resources/application.properties b/dashboard/webapp-backend/src/main/resources/application.properties
new file mode 100644 (file)
index 0000000..e969968
--- /dev/null
@@ -0,0 +1,73 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+# Defines RIC Dashboard property keys and default values.
+# Create a copy in the launch directory to override values; or
+# copy to "application-abc.properties" as mentioned in the README.
+
+# A spring property but without a "spring" prefix;
+# the port number is chosen RANDOMLY when running tests
+server.port = 8080
+
+# path to file that stores user details;
+# use a persistent volume in a K8S deployment
+userfile = users.json
+
+# boolean flag whether to enforce Portal user and roles on requests
+portalapi.security = true
+# class that decrypts ciphertext from Portal
+portalapi.decryptor = org.oransc.ric.portal.dashboard.portalapi.PortalSdkDecryptorAes
+# name of request cookie with user ID
+portalapi.usercookie = UserId
+
+# portal credentials must be supplied at deployment time
+portalapi.appname = RIC Dashboard
+portalapi.username =
+portalapi.password =
+
+# endpoint URLs must be supplied at deployment time
+# A1 Mediator
+a1med.url.prefix = http://jar-app-props-default-A1-URL-prefix
+a1med.url.suffix =
+# endpoint URLs must be supplied at deployment time
+# A1 Controller
+a1controller.url.prefix = http://localhost:8282
+a1controller.url.suffix = /restconf/operations
+# ANR xApp
+anrxapp.url.prefix = http://jar-app-props-default-ANR-URL-prefix
+anrxapp.url.suffix =
+# App Manager
+appmgr.url.prefix = http://jar-app-props-default-Xapp-Mgr-URL
+appmgr.url.suffix = /ric/v1
+# E2 Manager
+e2mgr.url.prefix = http://jar-app-props-default-E2-URL
+e2mgr.url.suffix = /v1
+# Kubernetes API via https://github.com/nokia/caas-ingress
+# Set insecure=true to disable SSL certificate and hostname checking
+caasingress.insecure = true
+caasingress.aux.url.prefix = https://jar-app-props-default-caas-ingress-aux-URL
+caasingress.aux.url.suffix = /api
+caasingress.plt.url.prefix = https://jar-app-props-default-caas-ingress-plt-URL
+caasingress.plt.url.suffix = /api
+
+# Mimic slow endpoints by defining sleep period, in milliseconds
+mock.config.delay = 0
+
+# Kibana report on metrics
+metrics.url.ac = http://jar-app-props-kibana-url-ac
+metrics.url.mc = http://jar-app-props-kibana-url-mc
diff --git a/dashboard/webapp-backend/src/main/resources/logback.xml b/dashboard/webapp-backend/src/main/resources/logback.xml
new file mode 100644 (file)
index 0000000..213d105
--- /dev/null
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<configuration>
+
+       <!-- Basic logback configuration for dev and test -->
+
+       <!-- component name is log file basename -->
+       <property name="componentName" value="ric-dashboard"></property>
+       <!-- gather files in a subdirectory -->
+       <property name="logDirectory" value="logs" />
+       <!-- output pattern -->
+       <property name="pattern" value="%d{&quot;yyyy-MM-dd'T'HH:mm:ss.SSSXXX&quot;, UTC} [%thread] %-5level %logger{36} - %msg%n"/>
+
+       <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+               <!-- defaults to type ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+               <encoder>
+                       <pattern>${pattern}</pattern>
+               </encoder>
+       </appender>
+
+       <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+               <file>${logDirectory}/${componentName}.log</file>
+               <append>true</append>
+               <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+                       <fileNamePattern>${logDirectory}/${componentName}.%i.log.zip</fileNamePattern>
+                       <minIndex>1</minIndex>
+                       <maxIndex>9</maxIndex>
+               </rollingPolicy>
+               <triggeringPolicy
+                       class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+                       <maxFileSize>10MB</maxFileSize>
+               </triggeringPolicy>
+               <!-- defaults to type ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+               <encoder>
+                       <pattern>${pattern}</pattern>
+               </encoder>
+       </appender>
+
+       <!-- Set default level for all loggers -->
+       <root level="INFO">
+               <appender-ref ref="CONSOLE" />
+               <appender-ref ref="FILE" />
+       </root>
+
+       <!-- Code under test should be chatty --> >
+       <logger name="org.oransc.ric.portal.dashboard" level="DEBUG" />
+
+       <!-- Watch authentication done by EPSDK-FW -->
+       <logger name="org.onap.portalsdk.core.onboarding.crossapi" level="DEBUG" />
+
+       <!-- Report request URLs -->
+       <logger name="org.springframework.web.client.RestTemplate" level="DEBUG" />
+
+       <!-- for debugging security
+       <logger name="org.springframework.security" level="DEBUG" />
+       -->
+
+</configuration>
diff --git a/dashboard/webapp-backend/src/main/resources/static/error.html b/dashboard/webapp-backend/src/main/resources/static/error.html
new file mode 100644 (file)
index 0000000..1b7633f
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<html>
+<head>
+<title>Static error page</title>
+<style>
+html, body {
+  font-family: Helvetica, Arial, sans-serif;
+}
+</style>
+</head>
+<body>
+<h2>Non RT RIC Dashboard Error</h2>
+<h4>The previous request could not be processed.</h4>
+<a href="/">Click here to reload the application</a>
+</body>
+</html>
diff --git a/dashboard/webapp-backend/src/main/resources/validation.properties b/dashboard/webapp-backend/src/main/resources/validation.properties
new file mode 100644 (file)
index 0000000..c7a5c79
--- /dev/null
@@ -0,0 +1,19 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+# empty file to suppress OWASP complaints emitted to stdout
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/DashboardTestServer.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/DashboardTestServer.java
new file mode 100644 (file)
index 0000000..df1a51c
--- /dev/null
@@ -0,0 +1,69 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard;
+
+import java.lang.invoke.MethodHandles;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * This class supports front-end web development. Placing this class in the test
+ * area allows excluding the mock configuration classes and the Mockito
+ * dependencies from the packaged version of the app.
+ *
+ * To launch a development server set the environment variable as listed below.
+ * This runs a Spring-Boot server with mock back-end beans, and keeps the server
+ * alive for manual testing. Supply this JVM argument:
+ *
+ * <pre>
+ * -Dorg.oransc.ric.portal.dashboard=mock
+ * </pre>
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
+@ActiveProfiles("test")
+public class DashboardTestServer {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       /*
+        * Keeps the test server alive forever. Use a guard so this test is never run by
+        * Jenkins.
+        */
+       @EnabledIfSystemProperty(named = "org.oransc.ric.portal.dashboard", matches = "mock")
+       @Test
+       public void keepServerAlive() {
+               logger.warn("Keeping server alive!");
+               try {
+                       synchronized (this) {
+                               this.wait();
+                       }
+               } catch (Exception ex) {
+                       logger.warn(ex.toString());
+               }
+       }
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1ControllerMockConfiguration.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/A1ControllerMockConfiguration.java
new file mode 100644 (file)
index 0000000..b056cb5
--- /dev/null
@@ -0,0 +1,424 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.oransc.ric.a1controller.client.api.A1ControllerApi;
+import org.oransc.ric.a1controller.client.invoker.ApiClient;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidPISchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidPIidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidPTidSchema;
+import org.oransc.ric.a1controller.client.model.InputNRRidSchema;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchema;
+import org.oransc.ric.a1controller.client.model.OutputDescNamePTSchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPISchema;
+import org.oransc.ric.a1controller.client.model.OutputPISchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPIidsListSchema;
+import org.oransc.ric.a1controller.client.model.OutputPIidsListSchemaOutput;
+import org.oransc.ric.a1controller.client.model.OutputPTidsListSchema;
+import org.oransc.ric.a1controller.client.model.OutputPTidsListSchemaOutput;
+import org.oransc.ric.portal.dashboard.model.PolicyType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.HttpStatus;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+/**
+ * Creates a mock implementation of the A1 controller client API.
+ */
+@Profile("test")
+@Configuration
+public class A1ControllerMockConfiguration {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // A "control" is an element in the XApp descriptor
+       public static final String AC_CONTROL_NAME = "admission_control_policy";
+
+       // Simulate remote method delay for UI testing
+       @Value("${mock.config.delay:0}")
+       private int delayMs;
+
+       public A1ControllerMockConfiguration() {
+               logger.info("Configuring mock A1 Mediator");
+       }
+
+       private ApiClient apiClient() {
+               ApiClient mockClient = mock(ApiClient.class);
+               when(mockClient.getStatusCode()).thenReturn(HttpStatus.OK);
+               return mockClient;
+       }
+
+       @Bean
+       // Use the same name as regular configuration
+       public A1ControllerApi a1ControllerApi() {
+               ApiClient apiClient = apiClient();
+               A1ControllerApi mockApi = mock(A1ControllerApi.class);
+
+               when(mockApi.getApiClient()).thenReturn(apiClient);
+
+               doAnswer(inv -> {
+                       if (delayMs > 0) {
+                               logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+                               Thread.sleep(delayMs);
+                       }
+                       List<Integer> types = database.getTypes();
+                       OutputPTidsListSchemaOutput output = new OutputPTidsListSchemaOutput();
+                       output.setPolicyTypeIdList(types);
+                       OutputPTidsListSchema outputSchema = new OutputPTidsListSchema();
+                       outputSchema.setOutput(output);
+                       return outputSchema;
+               }).when(mockApi).a1ControllerGetAllPolicyTypes(any(InputNRRidSchema.class));
+
+               doAnswer(inv -> {
+                       if (delayMs > 0) {
+                               logger.debug("a1ControllerGetPolicyType sleeping {}", delayMs);
+                               Thread.sleep(delayMs);
+                       }
+                       InputNRRidPTidSchema input = inv.<InputNRRidPTidSchema>getArgument(0);
+                       PolicyType policyType = database.getPolicyType(input.getInput().getPolicyTypeId());
+                       OutputDescNamePTSchemaOutput type = new OutputDescNamePTSchemaOutput();
+                       type.setName(policyType.getName());
+                       type.setDescription(policyType.getDescription());
+                       type.setPolicyType(database.normalize(policyType.getCreateSchema()));
+                       OutputDescNamePTSchema outputSchema = new OutputDescNamePTSchema();
+                       outputSchema.setOutput(type);
+                       return outputSchema;
+               }).when(mockApi).a1ControllerGetPolicyType(any(InputNRRidPTidSchema.class));
+
+               doAnswer(inv -> {
+                       if (delayMs > 0) {
+                               logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+                               Thread.sleep(delayMs);
+                       }
+                       InputNRRidPTidSchema input = inv.<InputNRRidPTidSchema>getArgument(0);
+                       List<String> instances = database.getInstances(Optional.of(input.getInput().getPolicyTypeId()));
+                       OutputPIidsListSchemaOutput instancesOutput = new OutputPIidsListSchemaOutput();
+                       instancesOutput.setPolicyInstanceIdList(instances);
+                       OutputPIidsListSchema outputSchema = new OutputPIidsListSchema();
+                       outputSchema.setOutput(instancesOutput);
+                       return outputSchema;
+               }).when(mockApi).a1ControllerGetAllInstancesForType(any(InputNRRidPTidSchema.class));
+
+               doAnswer(inv -> {
+                       if (delayMs > 0) {
+                               logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+                               Thread.sleep(delayMs);
+                       }
+                       InputNRRidPTidPIidSchema input = inv.<InputNRRidPTidPIidSchema>getArgument(0);
+                       Integer polcyTypeId = input.getInput().getPolicyTypeId();
+                       String instanceId = input.getInput().getPolicyInstanceId();
+                       String instance = database.normalize(database.getInstance(polcyTypeId, instanceId));
+                       OutputPISchemaOutput instanceOutput = new OutputPISchemaOutput();
+                       instanceOutput.setPolicyInstance(instance);
+                       OutputPISchema outputSchema = new OutputPISchema();
+                       outputSchema.setOutput(instanceOutput);
+                       return outputSchema;
+               }).when(mockApi).a1ControllerGetPolicyInstance(any(InputNRRidPTidPIidSchema.class));
+
+               doAnswer(inv -> {
+                       if (delayMs > 0) {
+                               logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+                               Thread.sleep(delayMs);
+                       }
+                       InputNRRidPTidPIidPISchema input = inv.<InputNRRidPTidPIidPISchema>getArgument(0);
+                       Integer polcyTypeId = input.getInput().getPolicyTypeId();
+                       String instanceId = input.getInput().getPolicyInstanceId();
+                       String instance = input.getInput().getPolicyInstance();
+                       database.putInstance(polcyTypeId, instanceId, instance);
+                       return null;
+               }).when(mockApi).a1ControllerCreatePolicyInstance(any(InputNRRidPTidPIidPISchema.class));
+
+               doAnswer(inv -> {
+                       if (delayMs > 0) {
+                               logger.debug("a1ControllerGetHandler sleeping {}", delayMs);
+                               Thread.sleep(delayMs);
+                       }
+                       InputNRRidPTidPIidSchema input = inv.<InputNRRidPTidPIidSchema>getArgument(0);
+                       Integer polcyTypeId = input.getInput().getPolicyTypeId();
+                       String instanceId = input.getInput().getPolicyInstanceId();
+                       database.deleteInstance(polcyTypeId, instanceId);
+                       return null;
+               }).when(mockApi).a1ControllerDeletePolicyInstance(any(InputNRRidPTidPIidSchema.class));
+
+               return mockApi;
+       }
+
+       class Database {
+
+               private String schema1 = "{\"$schema\": " //
+                               + "\"http://json-schema.org/draft-07/schema#\"," //
+                               + "\"title\": \"ANR\"," //
+                               + "\"description\": \"ANR Neighbour Cell Relation Policy\"," //
+                               + "\"type\": \"object\"," //
+                               + "\"properties\": " //
+                               + "{ \"servingCellNrcgi\": {" //
+                               + "\"type\": \"string\"," //
+                               + "\"description\" : \"Serving Cell Identifier (NR CGI)\"}," //
+                               + "\"neighborCellNrpci\": {" //
+                               + "\"type\": \"string\"," //
+                               + "\"description\": \"Neighbor Cell Identifier (NR PCI)\"}," //
+                               + "\"neighborCellNrcgi\": {" //
+                               + "\"type\": \"string\"," //
+                               + "\"description\": \"Neighbor Cell Identifier (NR CGI)\"}," //
+                               + "\"flagNoHo\": {" //
+                               + "\"type\": \"boolean\"," //
+                               + "\"description\": \"Flag for HANDOVER NOT ALLOWED\"}," //
+                               + "\"flagNoXn\": {" //
+                               + "\"type\": \"boolean\"," //
+                               + "\"description\": \"Flag for Xn CONNECTION NOT ALLOWED\"}," //
+                               + "\"flagNoRemove\": {" //
+                               + "\"type\": \"boolean\"," //
+                               + "\"description\": \"Flag for DELETION NOT ALLOWED\"}}, " //
+                               + "\"required\": [ \"servingCellNrcgi\",\"neighborCellNrpci\",\"neighborCellNrcgi\",\"flagNoHo\",\"flagNoXn\",\"flagNoRemove\" ]}";
+               private PolicyType policy1 = new PolicyType(1, "ANR", "ANR Neighbour Cell Relation Policy", schema1);
+
+               private String policyInstance1 = "{\"servingCellNrcgi\": \"Cell1\",\r\n" + //
+                               "\"neighborCellNrpci\": \"NCell1\",\r\n" + //
+                               "\"neighborCellNrcgi\": \"Ncell1\",\r\n" + //
+                               "\"flagNoHo\": true,\r\n" + //
+                               "\"flagNoXn\": true,\r\n" + //
+                               "\"flagNoRemove\": true}";
+
+               private String schema2 = "{\n" + "          \"type\": \"object\",\n" + //
+                               "          \"title\": \"Car\",\n" + //
+                               "          \"properties\": {\n" + //
+                               "            \"make\": {\n" + //
+                               "              \"type\": \"string\",\n" + //
+                               "              \"enum\": [\n" + //
+                               "                \"Toyota\",\n" + //
+                               "                \"BMW\",\n" + //
+                               "                \"Honda\",\n" + //
+                               "                \"Ford\",\n" + //
+                               "                \"Chevy\",\n" + //
+                               "                \"VW\"\n" + //
+                               "              ]\n" + //
+                               "            },\n" + //
+                               "            \"model\": {\n" + //
+                               "              \"type\": \"string\"\n" + //
+                               "            },\n" + //
+                               "            \"year\": {\n" + //
+                               "              \"type\": \"integer\",\n" + //
+                               "              \"enum\": [\n" + //
+                               "                1995,1996,1997,1998,1999,\n" + //
+                               "                2000,2001,2002,2003,2004,\n" + //
+                               "                2005,2006,2007,2008,2009,\n" + //
+                               "                2010,2011,2012,2013,2014\n" + //
+                               "              ],\n" + //
+                               "              \"default\": 2008\n" + //
+                               "            },\n" + //
+                               "            \"safety\": {\n" + //
+                               "              \"type\": \"integer\",\n" + //
+                               "              \"format\": \"rating\",\n" + //
+                               "              \"maximum\": 5,\n" + //
+                               "              \"exclusiveMaximum\": false,\n" + //
+                               "              \"readonly\": false\n" + //
+                               "            }\n" + //
+                               "          }\n" + //
+                               "        }\n";
+               private PolicyType policy2 = new PolicyType(2, "type2", "Type2 description", schema2);
+
+               private String schema3 = "{\n" + //
+                               "  \"$id\": \"https://example.com/person.schema.json\",\n" + //
+                               "  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n" + //
+                               "  \"title\": \"Person\",\n" + //
+                               "  \"type\": \"object\",\n" + //
+                               "  \"properties\": {\n" + //
+                               "    \"firstName\": {\n" + //
+                               "      \"type\": \"string\",\n" + //
+                               "      \"description\": \"The person's first name.\"\n" + //
+                               "    },\n" + //
+                               "    \"lastName\": {\n" + //
+                               "      \"type\": \"string\",\n" + //
+                               "      \"description\": \"The person's last name.\"\n" + //
+                               "    },\n" + //
+                               "    \"age\": {\n" + //
+                               "      \"description\": \"Age in years which must be equal to or greater than zero.\",\n" + //
+                               "      \"type\": \"integer\",\n" + //
+                               "      \"minimum\": 0\n" + //
+                               "    }\n" + //
+                               "  }\n" + //
+                               "}";
+               private PolicyType policy3 = new PolicyType(3, "type3", "Type3 description", schema3);
+
+               private String schema4 = "{" + //
+                               "                 \"$id\": \"https://example.com/arrays.schema.json\"," + //
+                               "                 \"$schema\": \"http://json-schema.org/draft-07/schema#\"," + //
+                               "                 \"description\": \"A representation of a person, company, organization, or place\"," + //
+                               "                 \"type\": \"object\"," + //
+                               "                 \"properties\": {" + //
+                               "                   \"fruits\": {" + //
+                               "                     \"type\": \"array\"," + //
+                               "                     \"items\": {" + //
+                               "                       \"type\": \"string\"" + //
+                               "                     }" + //
+                               "                   }," + //
+                               "                   \"vegetables\": {" + //
+                               "                     \"type\": \"array\"," + //
+                               "                     \"items\": { \"$ref\": \"#/definitions/veggie\" }" + //
+                               "                   }" + //
+                               "                 }," + //
+                               "                 \"definitions\": {" + //
+                               "                   \"veggie\": {" + //
+                               "                     \"type\": \"object\"," + //
+                               "                     \"required\": [ \"veggieName\", \"veggieLike\" ]," + //
+                               "                     \"properties\": {" + //
+                               "                       \"veggieName\": {" + //
+                               "                         \"type\": \"string\"," + //
+                               "                         \"description\": \"The name of the vegetable.\"" + //
+                               "                       }," + //
+                               "                       \"veggieLike\": {" + //
+                               "                         \"type\": \"boolean\"," + //
+                               "                         \"description\": \"Do I like this vegetable?\"" + //
+                               "                       }" + //
+                               "                     }" + //
+                               "                   }" + //
+                               "                 }" + //
+                               "               }";
+               private PolicyType policy4 = new PolicyType(4, "type4", "Type4 description", schema4);
+
+               public class PolicyException extends Exception {
+
+                       private static final long serialVersionUID = 1L;
+
+                       public PolicyException(String message) {
+                               super(message);
+                               System.out.println("**** Exception " + message);
+                       }
+               }
+
+               private class PolicyTypeHolder {
+                       PolicyTypeHolder(PolicyType pt) {
+                               this.policyType = pt;
+                       }
+
+                       String getInstance(String instanceId) throws PolicyException {
+                               String instance = instances.get(instanceId);
+                               if (instance == null) {
+                                       throw new PolicyException("Instance not found: " + instanceId);
+                               }
+                               return instance;
+                       }
+
+                       PolicyType getPolicyType() {
+                               return policyType;
+                       }
+
+                       void putInstance(String id, String data) {
+                               instances.put(id, data);
+                       }
+
+                       void deleteInstance(String id) {
+                               instances.remove(id);
+                       }
+
+                       List<String> getInstances() {
+                               return new ArrayList<>(instances.keySet());
+                       }
+
+                       private final PolicyType policyType;
+                       private Map<String, String> instances = new HashMap<>();
+               }
+
+               Database() {
+                       types.put(1, new PolicyTypeHolder(policy1));
+                       types.put(2, new PolicyTypeHolder(policy2));
+                       types.put(3, new PolicyTypeHolder(policy3));
+                       types.put(4, new PolicyTypeHolder(policy4));
+                       try {
+                               putInstance(1, "ANR-1", policyInstance1);
+                       } catch (JsonProcessingException | PolicyException e) {
+                               // Nothing
+                       }
+               }
+
+               String normalize(String str) {
+                       return str.replace('\n', ' ');
+               }
+
+               void putInstance(Integer typeId, String instanceId, String instanceData)
+                               throws JsonProcessingException, PolicyException {
+                       PolicyTypeHolder type = getTypeHolder(typeId);
+                       type.putInstance(instanceId, instanceData);
+               }
+
+               void deleteInstance(Integer typeId, String instanceId) throws JsonProcessingException, PolicyException {
+                       PolicyTypeHolder type = getTypeHolder(typeId);
+                       type.deleteInstance(instanceId);
+               }
+
+               String getInstance(Integer typeId, String instanceId) throws JsonProcessingException, PolicyException {
+                       return getTypeHolder(typeId).getInstance(instanceId);
+               }
+
+               List<Integer> getTypes() {
+                       return new ArrayList<>(types.keySet());
+               }
+
+               List<String> getInstances(Optional<Integer> typeId) throws PolicyException {
+                       if (typeId.isPresent()) {
+                               return getTypeHolder(typeId.get()).getInstances();
+                       } else {
+                               Set<String> res = new HashSet<String>();
+                               for (Iterator<PolicyTypeHolder> i = types.values().iterator(); i.hasNext();) {
+                                       res.addAll(i.next().getInstances());
+                               }
+                               return new ArrayList<>(res);
+                       }
+               }
+
+               private PolicyTypeHolder getTypeHolder(Integer typeId) throws PolicyException {
+                       PolicyTypeHolder typeHolder = types.get(typeId);
+                       if (typeHolder == null) {
+                               throw new PolicyException("Type not found: " + typeId);
+                       }
+                       return typeHolder;
+               }
+
+               private PolicyType getPolicyType(Integer typeId) throws PolicyException {
+                       PolicyTypeHolder typeHolder = getTypeHolder(typeId);
+                       return typeHolder.getPolicyType();
+               }
+
+               private Map<Integer, PolicyTypeHolder> types = new HashMap<>();
+
+       }
+
+       private final Database database = new Database();
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/PortalApIMockConfiguration.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/PortalApIMockConfiguration.java
new file mode 100644 (file)
index 0000000..a3d05be
--- /dev/null
@@ -0,0 +1,83 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.oransc.ric.portal.dashboard.portalapi.PortalAuthManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Configuration
+@Profile("test")
+public class PortalApIMockConfiguration {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // Unfortunately EPSDK-FW does not define these as constants
+       public static final String PORTAL_USERNAME_HEADER_KEY = "username";
+       public static final String PORTAL_PASSWORD_HEADER_KEY = "password";
+
+       @Bean
+       public ServletRegistrationBean<PortalRestAPIProxy> portalApiProxyServlet() {
+               PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+               final ServletRegistrationBean<PortalRestAPIProxy> servletBean = new ServletRegistrationBean<>(servlet,
+                               PortalApiConstants.API_PREFIX + "/*");
+               servletBean.setName("PortalRestApiProxyServlet");
+               return servletBean;
+       }
+
+       @Bean
+       public PortalAuthManager portalAuthManager() throws Exception {
+               PortalAuthManager mockManager = mock(PortalAuthManager.class);
+               final Map<String, String> credentialsMap = new HashMap<>();
+               credentialsMap.put("appName", "appName");
+               credentialsMap.put(PORTAL_USERNAME_HEADER_KEY, PORTAL_USERNAME_HEADER_KEY);
+               credentialsMap.put(PORTAL_PASSWORD_HEADER_KEY, PORTAL_PASSWORD_HEADER_KEY);
+               doAnswer(inv -> {
+                       logger.debug("getAppCredentials");
+                       return credentialsMap;
+               }).when(mockManager).getAppCredentials();
+               doAnswer(inv -> {
+                       logger.debug("getUserId");
+                       return "userId";
+               }).when(mockManager).validateEcompSso(any(HttpServletRequest.class));
+               doAnswer(inv -> {
+                       logger.debug("getAppCredentials");
+                       return credentialsMap;
+               }).when(mockManager).getAppCredentials();
+               return mockManager;
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/config/WebSecurityMockConfiguration.java
new file mode 100644 (file)
index 0000000..80cde66
--- /dev/null
@@ -0,0 +1,84 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.config;
+
+import java.lang.invoke.MethodHandles;
+
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("test")
+public class WebSecurityMockConfiguration extends WebSecurityConfigurerAdapter {
+
+       public static final String TEST_CRED_ADMIN = "admin";
+       public static final String TEST_CRED_STANDARD = "standard";
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       public WebSecurityMockConfiguration(@Value("${userfile}") final String userFilePath) {
+               logger.debug("ctor: user file path {}", userFilePath);
+       }
+
+       @Override
+       protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+               PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+               auth.inMemoryAuthentication() //
+                               .passwordEncoder(encoder) //
+                               // The admin user has the admin AND standard roles
+                               .withUser(TEST_CRED_ADMIN) //
+                               .password(encoder.encode(TEST_CRED_ADMIN))
+                               .roles(DashboardConstants.ROLE_NAME_ADMIN, DashboardConstants.ROLE_NAME_STANDARD)//
+                               .and()//
+                               // The standard user has only the standard role
+                               .withUser(TEST_CRED_STANDARD) //
+                               .password(encoder.encode(TEST_CRED_STANDARD)) //
+                               .roles(DashboardConstants.ROLE_NAME_STANDARD);
+       }
+
+       @Override
+       protected void configure(HttpSecurity http) throws Exception {
+               http.authorizeRequests().anyRequest().authenticated()//
+                               .and().httpBasic() //
+                               .and().csrf().disable();
+       }
+
+       @Override
+       public void configure(WebSecurity web) throws Exception {
+               // This disables Spring security, but not the app's filter.
+               web.ignoring().antMatchers(WebSecurityConfiguration.OPEN_PATHS);
+               web.ignoring().antMatchers("/", "/csrf"); // allow swagger-ui to load
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/AbstractControllerTest.java
new file mode 100644 (file)
index 0000000..d4163f0
--- /dev/null
@@ -0,0 +1,112 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.util.Map;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.oransc.ric.portal.dashboard.config.WebSecurityMockConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+// Need the fake answers from the backend
+@ActiveProfiles("test")
+public class AbstractControllerTest {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       // Created by Spring black magic
+       // https://spring.io/guides/gs/testing-web/
+       @LocalServerPort
+       private int localServerPort;
+
+       @Autowired
+       protected TestRestTemplate restTemplate;
+
+       /**
+        * Flexible URI builder.
+        * 
+        * @param queryParams
+        *                        Map of string-string query parameters
+        * @param path
+        *                        Array of path components. If a component has an
+        *                        embedded slash, the string is split and each
+        *                        subcomponent is added individually.
+        * @return URI
+        */
+       protected URI buildUri(final Map<String, String> queryParams, final String... path) {
+               UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://localhost:" + localServerPort + "/");
+               for (int p = 0; p < path.length; ++p) {
+                       if (path[p] == null || path[p].isEmpty()) {
+                               throw new IllegalArgumentException("Unexpected null or empty at path index " + Integer.toString(p));
+                       } else if (path[p].contains("/")) {
+                               String[] subpaths = path[p].split("/");
+                               for (String s : subpaths)
+                                       if (!s.isEmpty())
+                                               builder.pathSegment(s);
+                       } else {
+                               builder.pathSegment(path[p]);
+                       }
+               }
+               if (queryParams != null && queryParams.size() > 0) {
+                       for (Map.Entry<String, String> entry : queryParams.entrySet()) {
+                               if (entry.getKey() == null || entry.getValue() == null)
+                                       throw new IllegalArgumentException("Unexpected null key or value");
+                               else
+                                       builder.queryParam(entry.getKey(), entry.getValue());
+                       }
+               }
+               return builder.build().encode().toUri();
+       }
+
+       // Because I put the annotations on this parent class,
+       // must define at least one test here.
+       @Test
+       public void contextLoads() {
+               // Silence Sonar warning about missing assertion.
+               Assertions.assertTrue(logger.isWarnEnabled());
+               logger.info("Context loads on mock profile");
+       }
+
+       public TestRestTemplate testRestTemplateAdminRole() {
+               return restTemplate.withBasicAuth(WebSecurityMockConfiguration.TEST_CRED_ADMIN,
+                               WebSecurityMockConfiguration.TEST_CRED_ADMIN);
+       }
+
+       public TestRestTemplate testRestTemplateStandardRole() {
+               return restTemplate.withBasicAuth(WebSecurityMockConfiguration.TEST_CRED_STANDARD,
+                               WebSecurityMockConfiguration.TEST_CRED_STANDARD);
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/DefaultContextTest.java
new file mode 100644 (file)
index 0000000..48c5931
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * Tests whether the default (not mock) configuration classes run to completion.
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest
+public class DefaultContextTest {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       @Test
+       public void contextLoads() {
+               // Silence Sonar warning about missing assertion.
+               Assertions.assertTrue(logger.isWarnEnabled());
+               logger.info("Context loads on default profile");
+       }
+
+}
diff --git a/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java b/dashboard/webapp-backend/src/test/java/org/oransc/ric/portal/dashboard/controller/PortalRestCentralServiceTest.java
new file mode 100644 (file)
index 0000000..dc80968
--- /dev/null
@@ -0,0 +1,117 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.ric.portal.dashboard.controller;
+
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.ric.portal.dashboard.DashboardConstants;
+import org.oransc.ric.portal.dashboard.config.PortalApIMockConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+
+public class PortalRestCentralServiceTest extends AbstractControllerTest {
+
+       private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+       @Test
+       public void getAnalyticsTest() {
+               // paths are hardcoded here exactly like the EPSDK-FW library :(
+               URI uri = buildUri(null, PortalApiConstants.API_PREFIX, "/analytics");
+               logger.info("Invoking {}", uri);
+               ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+               // No Portal is available so this always fails
+               Assertions.assertTrue(response.getStatusCode().is4xxClientError());
+       }
+
+       @Test
+       public void getErrorPageTest() {
+               // Send unauthorized request
+               URI uri = buildUri(null, "/favicon.ico");
+               logger.info("Invoking {}", uri);
+               ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
+               Assertions.assertTrue(response.getStatusCode().is4xxClientError());
+               Assertions.assertTrue(response.getBody().contains("Static error page"));
+       }
+
+       private HttpEntity<Object> getEntityWithHeaders(Object body) {
+               HttpHeaders headers = new HttpHeaders();
+               headers.set(PortalApIMockConfiguration.PORTAL_USERNAME_HEADER_KEY,
+                               PortalApIMockConfiguration.PORTAL_USERNAME_HEADER_KEY);
+               headers.set(PortalApIMockConfiguration.PORTAL_PASSWORD_HEADER_KEY,
+                               PortalApIMockConfiguration.PORTAL_PASSWORD_HEADER_KEY);
+               HttpEntity<Object> entity = new HttpEntity<>(body, headers);
+               return entity;
+       }
+
+       private EcompUser createEcompUser(String loginId) {
+               EcompUser user = new EcompUser();
+               user.setLoginId(loginId);
+               EcompRole role = new EcompRole();
+               role.setRoleFunctions(Collections.EMPTY_SET);
+               role.setId(1L);
+               role.setName(DashboardConstants.ROLE_NAME_ADMIN);
+               Set<EcompRole> roles = new HashSet<>();
+               roles.add(role);
+               user.setRoles(roles);
+               return user;
+       }
+
+/*     @Test
+       public void createUserTest() {
+               final String loginId = "login1";
+               URI create = buildUri(null, PortalApiConstants.API_PREFIX, "user");
+               logger.info("Invoking {}", create);
+               HttpEntity<Object> requestEntity = getEntityWithHeaders(createEcompUser(loginId));
+               ResponseEntity<String> response = restTemplate.exchange(create, HttpMethod.POST, requestEntity, String.class);
+               Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+       }
+
+       @Test
+       public void updateUserTest() {
+               final String loginId = "login2";
+               URI create = buildUri(null, PortalApiConstants.API_PREFIX, "user");
+               EcompUser user = createEcompUser(loginId);
+               logger.info("Invoking {}", create);
+               HttpEntity<Object> requestEntity = getEntityWithHeaders(user);
+               // Create
+               ResponseEntity<String> response = restTemplate.exchange(create, HttpMethod.POST, requestEntity, String.class);
+               Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+               URI update = buildUri(null, PortalApiConstants.API_PREFIX, "user", loginId);
+               user.setEmail("user@company.org");
+               requestEntity = getEntityWithHeaders(user);
+               response = restTemplate.exchange(update, HttpMethod.POST, requestEntity, String.class);
+               Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
+       }
+*/
+
+}
diff --git a/dashboard/webapp-backend/src/test/resources/anr-policy-instance.json b/dashboard/webapp-backend/src/test/resources/anr-policy-instance.json
new file mode 100644 (file)
index 0000000..0d7315e
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "servingCellNrcgi": "Cell1",
+  "neighborCellNrpci": "NCell1",
+  "neighborCellNrcgi": "Ncell1",
+  "flagNoHo": true,
+  "flagNoXn": true,
+  "flagNoRemove": true
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/anr-policy-schema.json b/dashboard/webapp-backend/src/test/resources/anr-policy-schema.json
new file mode 100644 (file)
index 0000000..6e0263d
--- /dev/null
@@ -0,0 +1,40 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "ANR",
+  "description": "ANR Neighbour Cell Relation Policy",
+  "type": "object",
+  "properties": {
+    "servingCellNrcgi": {
+      "type": "string",
+      "description": "Serving Cell Identifier (NR CGI)"
+    },
+    "neighborCellNrpci": {
+      "type": "string",
+      "description": "Neighbor Cell Identifier (NR PCI)"
+    },
+    "neighborCellNrcgi": {
+      "type": "string",
+      "description": "Neighbor Cell Identifier (NR CGI)"
+    },
+    "flagNoHo": {
+      "type": "boolean",
+      "description": "Flag for HANDOVER NOT ALLOWED"
+    },
+    "flagNoXn": {
+      "type": "boolean",
+      "description": "Flag for Xn CONNECTION NOT ALLOWED"
+    },
+    "flagNoRemove": {
+      "type": "boolean",
+      "description": "Flag for DELETION NOT ALLOWED"
+    }
+  },
+  "required": [
+    "servingCellNrcgi",
+    "neighborCellNrpci",
+    "neighborCellNrcgi",
+    "flagNoHo",
+    "flagNoXn",
+    "flagNoRemove"
+  ]
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/caas-ingress-ricaux-pods.json b/dashboard/webapp-backend/src/test/resources/caas-ingress-ricaux-pods.json
new file mode 100644 (file)
index 0000000..216b7db
--- /dev/null
@@ -0,0 +1,9 @@
+{
+       "kind": "PodList",
+       "apiVersion": "v1",
+       "metadata": {
+               "selfLink": "/api/v1/namespaces/ricaux/pods",
+               "resourceVersion": "1594484"
+       },
+       "items": []
+}
diff --git a/dashboard/webapp-backend/src/test/resources/caas-ingress-ricplt-pods.json b/dashboard/webapp-backend/src/test/resources/caas-ingress-ricplt-pods.json
new file mode 100644 (file)
index 0000000..cf9c1a0
--- /dev/null
@@ -0,0 +1,1847 @@
+{
+       "kind": "PodList",
+       "apiVersion": "v1",
+       "metadata": {
+               "selfLink": "/api/v1/namespaces/ricplt/pods",
+               "resourceVersion": "3189380"
+       },
+       "items": [
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-a1mediator-74d9fc4c8c-77vtp",
+                               "generateName": "deployment-ricplt-a1mediator-74d9fc4c8c-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-a1mediator-74d9fc4c8c-77vtp",
+                               "uid": "487c582d-36d6-406a-92a3-bfbce04b83de",
+                               "resourceVersion": "2945631",
+                               "creationTimestamp": "2019-10-18T16:15:04Z",
+                               "labels": {
+                                       "app": "ricplt-a1mediator",
+                                       "group": "nagios",
+                                       "pod-template-hash": "74d9fc4c8c",
+                                       "release": "r1-a1mediator"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-a1mediator-74d9fc4c8c",
+                                               "uid": "84ef8695-3eb2-4dcd-b214-9d00ab5fb6b3",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "a1conf",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-a1mediator-a1conf",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-a1mediator",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-a1:0.10.3",
+                                               "ports": [
+                                                       {
+                                                               "name": "http",
+                                                               "containerPort": 10000,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrroute",
+                                                               "containerPort": 4561,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrdata",
+                                                               "containerPort": 4562,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-a1mediator-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "a1conf",
+                                                               "mountPath": "/opt/ricmanifest.json",
+                                                               "subPath": "ricmanifest.json"
+                                                       },
+                                                       {
+                                                               "name": "a1conf",
+                                                               "mountPath": "/opt/rmr_string_int_mapping.txt",
+                                                               "subPath": "rmr_string_int_mapping.txt"
+                                                       },
+                                                       {
+                                                               "name": "a1conf",
+                                                               "mountPath": "/opt/route/local.rt",
+                                                               "subPath": "local.rt"
+                                                       },
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "livenessProbe": {
+                                                       "httpGet": {
+                                                               "path": "/a1-p/healthcheck",
+                                                               "port": "http",
+                                                               "scheme": "HTTP"
+                                                       },
+                                                       "timeoutSeconds": 1,
+                                                       "periodSeconds": 10,
+                                                       "successThreshold": 1,
+                                                       "failureThreshold": 3
+                                               },
+                                               "readinessProbe": {
+                                                       "httpGet": {
+                                                               "path": "/a1-p/healthcheck",
+                                                               "port": "http",
+                                                               "scheme": "HTTP"
+                                                       },
+                                                       "timeoutSeconds": 1,
+                                                       "periodSeconds": 10,
+                                                       "successThreshold": 1,
+                                                       "failureThreshold": 3
+                                               },
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.202",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "a1mediator",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:04Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:09Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:09Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:04Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.202",
+                               "podIP": "10.244.4.71",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.4.71"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:04Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-a1mediator",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:05Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-a1:0.10.3",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-a1@sha256:64a61ed84d4d05dfa1690bb45949da333d7b088e2e12dbba0ce60c06445f52cb",
+                                               "containerID": "docker://07ceed10ee2a4413c167951e458b59a505a073cce82ac543146e58b698489d59",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-appmgr-6ccf55b98b-kbkt4",
+                               "generateName": "deployment-ricplt-appmgr-6ccf55b98b-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-appmgr-6ccf55b98b-kbkt4",
+                               "uid": "4e084e8a-eb0a-4ea2-9cc1-7f812cd6bb28",
+                               "resourceVersion": "2945633",
+                               "creationTimestamp": "2019-10-18T16:15:01Z",
+                               "labels": {
+                                       "app": "ricplt-appmgr",
+                                       "group": "nagios",
+                                       "pod-template-hash": "6ccf55b98b",
+                                       "release": "r1-appmgr"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-appmgr-6ccf55b98b",
+                                               "uid": "a169ebc4-9b7c-4b8d-81ed-6364e07df24e",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "config-volume",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-appmgr-appconfig",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "cert-volume",
+                                               "configMap": {
+                                                       "name": "xapp-mgr-certs",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "secret-volume",
+                                               "secret": {
+                                                       "secretName": "xapp-mgr-creds",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "helm-secret-volume",
+                                               "emptyDir": {
+                                                       
+                                               }
+                                       },
+                                       {
+                                               "name": "appmgr-bin-volume",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-appmgr-bin",
+                                                       "defaultMode": 493
+                                               }
+                                       },
+                                       {
+                                               "name": "svcacct-ricplt-appmgr-token-kclzw",
+                                               "secret": {
+                                                       "secretName": "svcacct-ricplt-appmgr-token-kclzw",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "initContainers": [
+                                       {
+                                               "name": "container-ricplt-appmgr-copy-tiller-secret",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/it-dep-init:0.0.1",
+                                               "command": [
+                                                       "/appmgr-tiller-secret-copier.sh"
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-appmgr-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "env": [
+                                                       {
+                                                               "name": "SVCACCT_NAME",
+                                                               "value": "svcacct-ricplt-appmgr"
+                                                       },
+                                                       {
+                                                               "name": "CLUSTER_NAME",
+                                                               "value": "kubernetes"
+                                                       },
+                                                       {
+                                                               "name": "KUBECONFIG",
+                                                               "value": "/tmp/kubeconfig"
+                                                       },
+                                                       {
+                                                               "name": "K8S_API_HOST",
+                                                               "value": "https://10.254.0.1:443"
+                                                       },
+                                                       {
+                                                               "name": "SECRET_NAMESPACE",
+                                                               "value": "ricinfra"
+                                                       },
+                                                       {
+                                                               "name": "SECRET_NAME",
+                                                               "value": "secret-helm-client-ricxapp"
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "helm-secret-volume",
+                                                               "mountPath": "/opt/ric/secret"
+                                                       },
+                                                       {
+                                                               "name": "appmgr-bin-volume",
+                                                               "mountPath": "/svcacct-to-kubeconfig.sh",
+                                                               "subPath": "svcacct-to-kubeconfig.sh"
+                                                       },
+                                                       {
+                                                               "name": "appmgr-bin-volume",
+                                                               "mountPath": "/appmgr-tiller-secret-copier.sh",
+                                                               "subPath": "appmgr-tiller-secret-copier.sh"
+                                                       },
+                                                       {
+                                                               "name": "svcacct-ricplt-appmgr-token-kclzw",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "IfNotPresent"
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-appmgr",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-appmgr:0.1.9",
+                                               "ports": [
+                                                       {
+                                                               "name": "http",
+                                                               "containerPort": 8080,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrroute",
+                                                               "containerPort": 4561,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrdata",
+                                                               "containerPort": 4560,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-appmgr-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "config-volume",
+                                                               "mountPath": "/opt/ric/config/appmgr.yaml",
+                                                               "subPath": "appmgr.yaml"
+                                                       },
+                                                       {
+                                                               "name": "cert-volume",
+                                                               "mountPath": "/opt/ric/certificates"
+                                                       },
+                                                       {
+                                                               "name": "helm-secret-volume",
+                                                               "mountPath": "/opt/ric/secret"
+                                                       },
+                                                       {
+                                                               "name": "secret-volume",
+                                                               "mountPath": "/opt/ric/secret/helm_repo_username",
+                                                               "subPath": "helm_repo_username"
+                                                       },
+                                                       {
+                                                               "name": "secret-volume",
+                                                               "mountPath": "/opt/ric/secret/helm_repo_password",
+                                                               "subPath": "helm_repo_password"
+                                                       },
+                                                       {
+                                                               "name": "svcacct-ricplt-appmgr-token-kclzw",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "svcacct-ricplt-appmgr",
+                               "serviceAccount": "svcacct-ricplt-appmgr",
+                               "nodeName": "172.29.16.203",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "appmgr",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:03Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:04Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:04Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:01Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.203",
+                               "podIP": "10.244.3.252",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.3.252"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:01Z",
+                               "initContainerStatuses": [
+                                       {
+                                               "name": "container-ricplt-appmgr-copy-tiller-secret",
+                                               "state": {
+                                                       "terminated": {
+                                                               "exitCode": 0,
+                                                               "reason": "Completed",
+                                                               "startedAt": "2019-10-18T16:15:02Z",
+                                                               "finishedAt": "2019-10-18T16:15:02Z",
+                                                               "containerID": "docker://130db0adfad526204726bf11fe24741d94f11f39f97f0d826b066ec852e5a452"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/it-dep-init:0.0.1",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/it-dep-init@sha256:d3a2c02660a0b5da5a7e38626c49018ca7f5e3bc39106b0728ff72245cd20be5",
+                                               "containerID": "docker://130db0adfad526204726bf11fe24741d94f11f39f97f0d826b066ec852e5a452"
+                                       }
+                               ],
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-appmgr",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:03Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-appmgr:0.1.9",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-appmgr@sha256:5c076f702d570b385d10200cda8d504475ce44eb1bcbb131b1d50e00eabae4d7",
+                                               "containerID": "docker://791f455c1974a100aaa09ab0a290e438d75aa1c3aadcb717c42d53e02cdedb83",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-dbaas-d4c9f7b88-7wgb9",
+                               "generateName": "deployment-ricplt-dbaas-d4c9f7b88-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-dbaas-d4c9f7b88-7wgb9",
+                               "uid": "e54e51fc-c4bd-4308-805e-16c2d588dacd",
+                               "resourceVersion": "2945634",
+                               "creationTimestamp": "2019-10-18T16:15:01Z",
+                               "labels": {
+                                       "app": "ricplt-dbaas",
+                                       "group": "nagios",
+                                       "pod-template-hash": "d4c9f7b88",
+                                       "release": "r1-dbaas"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-dbaas-d4c9f7b88",
+                                               "uid": "7e8d5d34-efa9-41fe-b92f-d9b71bc40360",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-dbaas",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-dbaas:0.1.0",
+                                               "ports": [
+                                                       {
+                                                               "name": "sql",
+                                                               "containerPort": 6379,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 0,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.202",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:01Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:03Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:03Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:01Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.202",
+                               "podIP": "10.244.4.70",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.4.70"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:01Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-dbaas",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:02Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-dbaas:0.1.0",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-dbaas@sha256:f63cfa353f355155ec6455a68d18c631900a2602bf7cc2ba35d6210971736b76",
+                                               "containerID": "docker://8972d8b61d5c3ff56b50814575647d70fb3307602506cda3e34b6734c28a3f36",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-e2mgr-86c76477c5-mf5t5",
+                               "generateName": "deployment-ricplt-e2mgr-86c76477c5-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-e2mgr-86c76477c5-mf5t5",
+                               "uid": "5f2231ea-bec0-46fc-a8f8-09b0b80e982f",
+                               "resourceVersion": "2945636",
+                               "creationTimestamp": "2019-10-18T16:15:02Z",
+                               "labels": {
+                                       "app": "ricplt-e2mgr",
+                                       "group": "nagios",
+                                       "pod-template-hash": "86c76477c5",
+                                       "release": "r1-e2mgr"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-e2mgr-86c76477c5",
+                                               "uid": "f7dfd4a3-4eb3-4c46-a6c8-adc4ae37ef57",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "local-router-file",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-e2mgr-router-configmap",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "local-configuration-file",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-e2mgr-configuration-configmap",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-e2mgr",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2mgr:2.0.7",
+                                               "ports": [
+                                                       {
+                                                               "name": "http",
+                                                               "containerPort": 3800,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrroute",
+                                                               "containerPort": 4561,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrdata",
+                                                               "containerPort": 3801,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-e2mgr-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "local-router-file",
+                                                               "mountPath": "/opt/E2Manager/router.txt",
+                                                               "subPath": "router.txt"
+                                                       },
+                                                       {
+                                                               "name": "local-configuration-file",
+                                                               "mountPath": "/opt/E2Manager/resources/configuration.yaml",
+                                                               "subPath": "configuration.yaml"
+                                                       },
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always",
+                                               "securityContext": {
+                                                       "privileged": false
+                                               },
+                                               "stdin": true,
+                                               "tty": true
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.204",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "e2mgr",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:02Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:05Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:05Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:02Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.204",
+                               "podIP": "10.244.2.100",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.2.100"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:02Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-e2mgr",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:04Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2mgr:2.0.7",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2mgr@sha256:87fa19a934215bdec71a355ef08eec9e273c992bab80af727f4f1b7a74ebacfa",
+                                               "containerID": "docker://ff3a2fcfc72b00e3c317899f2b620da2f65e3de260623daed7825f6a74dbcb5c",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-e2term-5dcf8b54b-5mkxl",
+                               "generateName": "deployment-ricplt-e2term-5dcf8b54b-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-e2term-5dcf8b54b-5mkxl",
+                               "uid": "8fae1dcd-5e42-4a66-be6f-e893d5563689",
+                               "resourceVersion": "2945639",
+                               "creationTimestamp": "2019-10-18T16:15:03Z",
+                               "labels": {
+                                       "app": "ricplt-e2term",
+                                       "group": "nagios",
+                                       "pod-template-hash": "5dcf8b54b",
+                                       "release": "r1-e2term"
+                               },
+                               "annotations": {
+                                       "danm.k8s.io/interfaces": "[\n  {\"clusterNetwork\":\"default\", \"ip\":\"dynamic\"},\n  {\"clusterNetwork\":\"ran\", \"ip\":\"dynamic\", \"ip6\":\"dynamic\"}\n]\n",
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-e2term-5dcf8b54b",
+                                               "uid": "cc20b8a0-6d74-4fb9-b384-bdce9c9ae184",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "local-router-file",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-e2term-router-configmap",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "localtime",
+                                               "hostPath": {
+                                                       "path": "/etc/localtime",
+                                                       "type": ""
+                                               }
+                                       },
+                                       {
+                                               "name": "pizpub-config",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-e2term-pizpub",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "vol-shared",
+                                               "persistentVolumeClaim": {
+                                                       "claimName": "pvc-ricplt-e2term"
+                                               }
+                                       },
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-e2term",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2:2.0.7.5",
+                                               "ports": [
+                                                       {
+                                                               "name": "rmrroute",
+                                                               "containerPort": 4561,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrdata",
+                                                               "containerPort": 38000,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-e2term-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "local-router-file",
+                                                               "mountPath": "/opt/e2/router.txt",
+                                                               "subPath": "router.txt"
+                                                       },
+                                                       {
+                                                               "name": "local-router-file",
+                                                               "mountPath": "/tmp/rmr_verbose",
+                                                               "subPath": "rmr_verbose"
+                                                       },
+                                                       {
+                                                               "name": "vol-shared",
+                                                               "mountPath": "/data/outgoing/",
+                                                               "subPath": "outgoing"
+                                                       },
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always",
+                                               "securityContext": {
+                                                       "privileged": false
+                                               },
+                                               "stdin": true,
+                                               "tty": true
+                                       },
+                                       {
+                                               "name": "container-ricplt-e2term-pizpub",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/pizpub:0.0.5155",
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "localtime",
+                                                               "readOnly": true,
+                                                               "mountPath": "/etc/localtime"
+                                                       },
+                                                       {
+                                                               "name": "vol-shared",
+                                                               "mountPath": "/data"
+                                                       },
+                                                       {
+                                                               "name": "pizpub-config",
+                                                               "mountPath": "/opt/app/config/conf/"
+                                                       },
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "lifecycle": {
+                                                       "postStart": {
+                                                               "exec": {
+                                                                       "command": [
+                                                                               "/bin/sh",
+                                                                               "/opt/app/config/conf/cleaner.sh",
+                                                                               "/data/sent",
+                                                                               "3"
+                                                                       ]
+                                                               }
+                                                       }
+                                               },
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirstWithHostNet",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.201",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "e2term",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:22Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:33Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:33Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:22Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.201",
+                               "podIP": "10.244.1.90",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.1.90"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:22Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-e2term",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:31Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "ranco-dev-tools.eastus.cloudapp.azure.com:10001/ric-plt-e2:2.0.7.5",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-e2@sha256:0ea1a356d018495a93e124ddd793e09626bf6e4d9b96355e731673ef7fab5a1f",
+                                               "containerID": "docker://bf9ca87dbad9436b0ed99ffe38036fb49033a9bc2cf2eb548397fbc9c48f1c3d",
+                                               "started": true
+                                       },
+                                       {
+                                               "name": "container-ricplt-e2term-pizpub",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:32Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "ranco-dev-tools.eastus.cloudapp.azure.com:10001/pizpub:0.0.5155",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/pizpub@sha256:138c2d2d25e6528c4a5a8a402c277722d1c1fd4d6792b644967acd538affb1ed",
+                                               "containerID": "docker://93e39661623b7afc8156008bb6fbc206458964a6eb0633f80164e4c7ef59fe76",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-jaegeradapter-6ff8676c7-m4qkf",
+                               "generateName": "deployment-ricplt-jaegeradapter-6ff8676c7-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-jaegeradapter-6ff8676c7-m4qkf",
+                               "uid": "d6a5e9e9-87d0-4d1e-b1b7-cc1a4f20dc2e",
+                               "resourceVersion": "2945640",
+                               "creationTimestamp": "2019-10-18T16:15:08Z",
+                               "labels": {
+                                       "app": "ricplt-jaegeradapter",
+                                       "group": "nagios",
+                                       "pod-template-hash": "6ff8676c7",
+                                       "release": "r1-jaegeradapter"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-jaegeradapter-6ff8676c7",
+                                               "uid": "98bc03d7-a082-4ac1-9b89-064022a37dff",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-jaegeradapter",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/all-in-one:1.12",
+                                               "ports": [
+                                                       {
+                                                               "name": "zipkincompact",
+                                                               "containerPort": 5775,
+                                                               "protocol": "UDP"
+                                                       },
+                                                       {
+                                                               "name": "jaegercompact",
+                                                               "containerPort": 6831,
+                                                               "protocol": "UDP"
+                                                       },
+                                                       {
+                                                               "name": "jaegerbinary",
+                                                               "containerPort": 6832,
+                                                               "protocol": "UDP"
+                                                       },
+                                                       {
+                                                               "name": "httpquery",
+                                                               "containerPort": 16686,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "httpconfig",
+                                                               "containerPort": 5778,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "zipkinhttp",
+                                                               "containerPort": 9411,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "jaegerhttp",
+                                                               "containerPort": 14268,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "jaegerhttpt",
+                                                               "containerPort": 14267,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-jaegeradapter"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "livenessProbe": {
+                                                       "httpGet": {
+                                                               "path": "/",
+                                                               "port": 16686,
+                                                               "scheme": "HTTP"
+                                                       },
+                                                       "timeoutSeconds": 1,
+                                                       "periodSeconds": 10,
+                                                       "successThreshold": 1,
+                                                       "failureThreshold": 3
+                                               },
+                                               "readinessProbe": {
+                                                       "httpGet": {
+                                                               "path": "/",
+                                                               "port": 16686,
+                                                               "scheme": "HTTP"
+                                                       },
+                                                       "timeoutSeconds": 1,
+                                                       "periodSeconds": 10,
+                                                       "successThreshold": 1,
+                                                       "failureThreshold": 3
+                                               },
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.203",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "jaegeradapter",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:08Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:15Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:15Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:08Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.203",
+                               "podIP": "10.244.3.254",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.3.254"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:08Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-jaegeradapter",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:09Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/all-in-one:1.12",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/all-in-one@sha256:51b901b653f4a4ca5dd50be9133c08dacf2d3eb092e507c213e7955a0e132297",
+                                               "containerID": "docker://95013a49a1705a503f5f7dde7a38fa7277523a73cdef96d264fcefe170e8a921",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-rsm-88477585f-qkkj7",
+                               "generateName": "deployment-ricplt-rsm-88477585f-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-rsm-88477585f-qkkj7",
+                               "uid": "d4c58ff4-743e-4ed6-bd36-aeb02daa1ca6",
+                               "resourceVersion": "2945642",
+                               "creationTimestamp": "2019-10-18T16:15:07Z",
+                               "labels": {
+                                       "app": "ricplt-rsm",
+                                       "group": "nagios",
+                                       "pod-template-hash": "88477585f",
+                                       "release": "r1-rsm"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-rsm-88477585f",
+                                               "uid": "1fe7de57-90d9-4898-9b71-1ae9c4a6f014",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "local-router-file",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-rsm-router-configmap",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "local-configuration-file",
+                                               "configMap": {
+                                                       "name": "configmap-ricplt-rsm",
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-rsm",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-rsm:2.0.7",
+                                               "ports": [
+                                                       {
+                                                               "name": "http",
+                                                               "containerPort": 4800,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrroute",
+                                                               "containerPort": 4561,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrdata",
+                                                               "containerPort": 4801,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-rsm-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "local-router-file",
+                                                               "mountPath": "/opt/RSM/router.txt",
+                                                               "subPath": "router.txt"
+                                                       },
+                                                       {
+                                                               "name": "local-configuration-file",
+                                                               "mountPath": "/opt/RSM/resources/configuration.yaml",
+                                                               "subPath": "configuration.yaml"
+                                                       },
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always",
+                                               "securityContext": {
+                                                       "privileged": false
+                                               },
+                                               "stdin": true,
+                                               "tty": true
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.203",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "rsm",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:07Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:09Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:09Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:07Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.203",
+                               "podIP": "10.244.3.253",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.3.253"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:07Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-rsm",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:08Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-rsm:2.0.7",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-rsm@sha256:e6fb3bc17fcd5a2fbc7d34eeb744fbfed4eaaaf6c669e084b379ee05368820d3",
+                                               "containerID": "docker://5e90673a6b2c292f2ce7c731bf8747c8a63f429eca08d08a993130001c7d6f5e",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-submgr-7549b87fb8-4t6mx",
+                               "generateName": "deployment-ricplt-submgr-7549b87fb8-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-submgr-7549b87fb8-4t6mx",
+                               "uid": "c6fbd48b-2757-421c-a534-f1931b04312b",
+                               "resourceVersion": "2945646",
+                               "creationTimestamp": "2019-10-18T16:15:05Z",
+                               "labels": {
+                                       "app": "ricplt-submgr",
+                                       "group": "nagios",
+                                       "pod-template-hash": "7549b87fb8",
+                                       "release": "r1-submgr"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-submgr-7549b87fb8",
+                                               "uid": "e2b9dd9f-cca4-4f64-9e11-b6ee174c4f6f",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "config-volume",
+                                               "configMap": {
+                                                       "name": "submgrcfg",
+                                                       "items": [
+                                                               {
+                                                                       "key": "submgrcfg",
+                                                                       "path": "submgr-config.yaml",
+                                                                       "mode": 420
+                                                               }
+                                                       ],
+                                                       "defaultMode": 420
+                                               }
+                                       },
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-submgr",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-submgr:0.10.5",
+                                               "command": [
+                                                       "/run_submgr.sh"
+                                               ],
+                                               "ports": [
+                                                       {
+                                                               "name": "http",
+                                                               "containerPort": 3800,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrroute",
+                                                               "containerPort": 4561,
+                                                               "protocol": "TCP"
+                                                       },
+                                                       {
+                                                               "name": "rmrdata",
+                                                               "containerPort": 4560,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-submgr-env"
+                                                               }
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "config-volume",
+                                                               "mountPath": "/cfg"
+                                                       },
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.205",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "submgr",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:05Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:07Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:07Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:05Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.205",
+                               "podIP": "10.244.0.168",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.0.168"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:05Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-submgr",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:07Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-submgr:0.10.5",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-submgr@sha256:aa8ada253d0800a849b6124fc54793815caaf93ad46b8d47cdd1f590ef69f813",
+                                               "containerID": "docker://724ba7834ef80d1f3c85ae7990ead480ed5226f0275816bae358edc9ddf54da6",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               },
+               {
+                       "metadata": {
+                               "name": "deployment-ricplt-vespamgr-55f6484b7-g5zfw",
+                               "generateName": "deployment-ricplt-vespamgr-55f6484b7-",
+                               "namespace": "ricplt",
+                               "selfLink": "/api/v1/namespaces/ricplt/pods/deployment-ricplt-vespamgr-55f6484b7-g5zfw",
+                               "uid": "a7ae0f4a-adfa-48c6-8c41-725ba1c84b11",
+                               "resourceVersion": "2945649",
+                               "creationTimestamp": "2019-10-18T16:15:06Z",
+                               "labels": {
+                                       "app": "ricplt-vespamgr",
+                                       "group": "nagios",
+                                       "pod-template-hash": "55f6484b7",
+                                       "release": "r1-vespamgr"
+                               },
+                               "annotations": {
+                                       "kubernetes.io/psp": "caas-default"
+                               },
+                               "ownerReferences": [
+                                       {
+                                               "apiVersion": "apps/v1",
+                                               "kind": "ReplicaSet",
+                                               "name": "deployment-ricplt-vespamgr-55f6484b7",
+                                               "uid": "dc5e0e81-23da-4fed-99da-14cb7a8fe06c",
+                                               "controller": true,
+                                               "blockOwnerDeletion": true
+                                       }
+                               ]
+                       },
+                       "spec": {
+                               "volumes": [
+                                       {
+                                               "name": "default-token-5j24g",
+                                               "secret": {
+                                                       "secretName": "default-token-5j24g",
+                                                       "defaultMode": 420
+                                               }
+                                       }
+                               ],
+                               "containers": [
+                                       {
+                                               "name": "container-ricplt-vespamgr",
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-vespamgr:0.0.5",
+                                               "ports": [
+                                                       {
+                                                               "name": "http",
+                                                               "containerPort": 8080,
+                                                               "protocol": "TCP"
+                                                       }
+                                               ],
+                                               "envFrom": [
+                                                       {
+                                                               "configMapRef": {
+                                                                       "name": "configmap-ricplt-vespamgr"
+                                                               }
+                                                       },
+                                                       {
+                                                               "secretRef": {
+                                                                       "name": "vespa-secrets"
+                                                               }
+                                                       }
+                                               ],
+                                               "env": [
+                                                       {
+                                                               "name": "VESMGR_APPMGRDOMAIN",
+                                                               "value": "appmgr-service"
+                                                       }
+                                               ],
+                                               "resources": {
+                                                       
+                                               },
+                                               "volumeMounts": [
+                                                       {
+                                                               "name": "default-token-5j24g",
+                                                               "readOnly": true,
+                                                               "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+                                                       }
+                                               ],
+                                               "livenessProbe": {
+                                                       "httpGet": {
+                                                               "path": "/supervision",
+                                                               "port": 8080,
+                                                               "scheme": "HTTP"
+                                                       },
+                                                       "initialDelaySeconds": 30,
+                                                       "timeoutSeconds": 20,
+                                                       "periodSeconds": 60,
+                                                       "successThreshold": 1,
+                                                       "failureThreshold": 3
+                                               },
+                                               "terminationMessagePath": "/dev/termination-log",
+                                               "terminationMessagePolicy": "File",
+                                               "imagePullPolicy": "Always"
+                                       }
+                               ],
+                               "restartPolicy": "Always",
+                               "terminationGracePeriodSeconds": 30,
+                               "dnsPolicy": "ClusterFirst",
+                               "serviceAccountName": "default",
+                               "serviceAccount": "default",
+                               "nodeName": "172.29.16.204",
+                               "securityContext": {
+                                       
+                               },
+                               "imagePullSecrets": [
+                                       {
+                                               "name": "docker-reg-cred"
+                                       }
+                               ],
+                               "hostname": "vespamgr",
+                               "schedulerName": "default-scheduler",
+                               "enableServiceLinks": true
+                       },
+                       "status": {
+                               "phase": "Running",
+                               "conditions": [
+                                       {
+                                               "type": "Initialized",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:06Z"
+                                       },
+                                       {
+                                               "type": "Ready",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:08Z"
+                                       },
+                                       {
+                                               "type": "ContainersReady",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:08Z"
+                                       },
+                                       {
+                                               "type": "PodScheduled",
+                                               "status": "True",
+                                               "lastProbeTime": null,
+                                               "lastTransitionTime": "2019-10-18T16:15:06Z"
+                                       }
+                               ],
+                               "hostIP": "172.29.16.204",
+                               "podIP": "10.244.2.101",
+                               "podIPs": [
+                                       {
+                                               "ip": "10.244.2.101"
+                                       }
+                               ],
+                               "startTime": "2019-10-18T16:15:06Z",
+                               "containerStatuses": [
+                                       {
+                                               "name": "container-ricplt-vespamgr",
+                                               "state": {
+                                                       "running": {
+                                                               "startedAt": "2019-10-18T16:15:08Z"
+                                                       }
+                                               },
+                                               "lastState": {
+                                                       
+                                               },
+                                               "ready": true,
+                                               "restartCount": 0,
+                                               "image": "registry.kube-system.svc.rec.io:5555/ric/ric-plt-vespamgr:0.0.5",
+                                               "imageID": "docker-pullable://registry.kube-system.svc.rec.io:5555/ric/ric-plt-vespamgr@sha256:97753ef72471a5fddd59ff35a2fe763b041848f6e83214acb78ad73c7316b371",
+                                               "containerID": "docker://80884c969cbf802945c075afc47d747b5e747e4645c691d376820c9d61094e7c",
+                                               "started": true
+                                       }
+                               ],
+                               "qosClass": "BestEffort"
+                       }
+               }
+       ]
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/demo-policy-schema-1.json b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-1.json
new file mode 100644 (file)
index 0000000..09999ef
--- /dev/null
@@ -0,0 +1,53 @@
+{
+  "type": "object",
+  "title": "Car",
+  "properties": {
+    "make": {
+      "type": "string",
+      "enum": [
+        "Toyota",
+        "BMW",
+        "Honda",
+        "Ford",
+        "Chevy",
+        "VW"
+      ]
+    },
+    "model": {
+      "type": "string"
+    },
+    "year": {
+      "type": "integer",
+      "enum": [
+        1995,
+        1996,
+        1997,
+        1998,
+        1999,
+        2000,
+        2001,
+        2002,
+        2003,
+        2004,
+        2005,
+        2006,
+        2007,
+        2008,
+        2009,
+        2010,
+        2011,
+        2012,
+        2013,
+        2014
+      ],
+      "default": 2008
+    },
+    "safety": {
+      "type": "integer",
+      "format": "rating",
+      "maximum": 5,
+      "exclusiveMaximum": false,
+      "readonly": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/demo-policy-schema-2.json b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-2.json
new file mode 100644 (file)
index 0000000..69ec678
--- /dev/null
@@ -0,0 +1,21 @@
+{
+  "$id": "https://example.com/person.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "Person",
+  "type": "object",
+  "properties": {
+    "firstName": {
+      "type": "string",
+      "description": "The person's first name."
+    },
+    "lastName": {
+      "type": "string",
+      "description": "The person's last name."
+    },
+    "age": {
+      "description": "Age in years which must be equal to or greater than zero.",
+      "type": "integer",
+      "minimum": 0
+    }
+  }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/demo-policy-schema-3.json b/dashboard/webapp-backend/src/test/resources/demo-policy-schema-3.json
new file mode 100644 (file)
index 0000000..3be9959
--- /dev/null
@@ -0,0 +1,39 @@
+{
+  "$id": "https://example.com/arrays.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "description": "A representation of a person, company, organization, or place",
+  "type": "object",
+  "properties": {
+    "fruits": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    },
+    "vegetables": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/veggie"
+      }
+    }
+  },
+  "definitions": {
+    "veggie": {
+      "type": "object",
+      "required": [
+        "veggieName",
+        "veggieLike"
+      ],
+      "properties": {
+        "veggieName": {
+          "type": "string",
+          "description": "The name of the vegetable."
+        },
+        "veggieLike": {
+          "type": "boolean",
+          "description": "Do I like this vegetable?"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-backend/src/test/resources/key.properties b/dashboard/webapp-backend/src/test/resources/key.properties
new file mode 100644 (file)
index 0000000..85a5689
--- /dev/null
@@ -0,0 +1,22 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+cipher.enc.key = bogus
diff --git a/dashboard/webapp-backend/src/test/resources/portal.properties b/dashboard/webapp-backend/src/test/resources/portal.properties
new file mode 100644 (file)
index 0000000..326539e
--- /dev/null
@@ -0,0 +1,26 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ========================LICENSE_END===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+portal.api.impl.class = org.oransc.ric.portal.dashboard.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+ecomp_redirect_url = https://www.wikipedia.org
+ecomp_rest_url = http://localhost/portal
+ueb_app_key = abcdef1234567890
diff --git a/dashboard/webapp-frontend/.gitignore b/dashboard/webapp-frontend/.gitignore
new file mode 100644 (file)
index 0000000..588ac10
--- /dev/null
@@ -0,0 +1,41 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node
+/node_modules
+
+/.classpath
+/.externalToolBuilders
+/.project
+/.settings
+/target/
+/.mvn/wrapper/maven-wrapper.jar
+**/.vscode
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/dashboard/webapp-frontend/README.md b/dashboard/webapp-frontend/README.md
new file mode 100644 (file)
index 0000000..f1c849e
--- /dev/null
@@ -0,0 +1,42 @@
+# RIC Dashboard Web Application Frontend
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.3.
+
+## Development server
+
+Run `./ng serve --proxy-config proxy.conf.json` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
+
+## License
+
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/dashboard/webapp-frontend/angular.json b/dashboard/webapp-frontend/angular.json
new file mode 100644 (file)
index 0000000..77bd4a8
--- /dev/null
@@ -0,0 +1,150 @@
+{
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+  "version": 1,
+  "newProjectRoot": "projects",
+  "projects": {
+    "dashApp": {
+      "root": "",
+      "sourceRoot": "src",
+      "projectType": "application",
+      "prefix": "rd",
+      "schematics": {
+        "@schematics/angular:component": {
+          "styleext": "scss"
+            }
+        },
+      "architect": {
+        "build": {
+          "builder": "@angular-devkit/build-angular:browser",
+          "options": {
+            "outputPath": "dist/dashApp",
+            "index": "src/index.html",
+            "main": "src/main.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "src/tsconfig.app.json",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss",
+              "node_modules/@fortawesome/fontawesome-free/scss/solid.scss",
+              "node_modules/@fortawesome/fontawesome-free/scss/regular.scss",
+              "node_modules/@fortawesome/fontawesome-free/scss/brands.scss",
+              "node_modules/angular-bootstrap-md/scss/bootstrap/bootstrap.scss",
+              "node_modules/angular-bootstrap-md/scss/mdb-free.scss",
+              "node_modules/material-design-icons/iconfont/material-icons.css",
+              "src/styles.scss",
+              "node_modules/ngx-toastr/toastr.css"
+            ],
+            "scripts": [
+            "node_modules/chart.js/dist/Chart.js",
+            "node_modules/hammerjs/hammer.min.js"
+            ]
+          },
+          "configurations": {
+            "production": {
+              "fileReplacements": [
+                {
+                  "replace": "src/environments/environment.ts",
+                  "with": "src/environments/environment.prod.ts"
+                }
+              ],
+              "optimization": true,
+              "outputHashing": "all",
+              "sourceMap": false,
+              "extractCss": true,
+              "namedChunks": false,
+              "aot": true,
+              "extractLicenses": true,
+              "vendorChunk": false,
+              "buildOptimizer": true,
+              "budgets": [
+                {
+                  "type": "initial",
+                  "maximumWarning": "2mb",
+                  "maximumError": "5mb"
+                }
+              ]
+            }
+          }
+        },
+        "serve": {
+          "builder": "@angular-devkit/build-angular:dev-server",
+          "options": {
+            "browserTarget": "dashApp:build"
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "dashApp:build:production"
+            }
+          }
+        },
+        "extract-i18n": {
+          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "options": {
+            "browserTarget": "dashApp:build"
+          }
+        },
+        "test": {
+          "builder": "@angular-devkit/build-angular:karma",
+          "options": {
+            "main": "src/test.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "src/tsconfig.spec.json",
+            "karmaConfig": "src/karma.conf.js",
+            "styles": [
+              "src/styles.css"
+            ],
+            "scripts": [],
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ]
+          }
+        },
+        "lint": {
+          "builder": "@angular-devkit/build-angular:tslint",
+          "options": {
+            "tsConfig": [
+              "src/tsconfig.app.json",
+              "src/tsconfig.spec.json"
+            ],
+            "exclude": [
+              "**/node_modules/**"
+            ]
+          }
+        }
+      }
+    },
+    "dashApp-e2e": {
+      "root": "e2e/",
+      "projectType": "application",
+      "prefix": "",
+      "architect": {
+        "e2e": {
+          "builder": "@angular-devkit/build-angular:protractor",
+          "options": {
+            "protractorConfig": "e2e/protractor.conf.js",
+            "devServerTarget": "dashApp:serve"
+          },
+          "configurations": {
+            "production": {
+              "devServerTarget": "dashApp:serve:production"
+            }
+          }
+        },
+        "lint": {
+          "builder": "@angular-devkit/build-angular:tslint",
+          "options": {
+            "tsConfig": "e2e/tsconfig.e2e.json",
+            "exclude": [
+              "**/node_modules/**"
+            ]
+          }
+        }
+      }
+    }
+  },
+  "defaultProject": "dashApp"
+}
diff --git a/dashboard/webapp-frontend/browserslist b/dashboard/webapp-frontend/browserslist
new file mode 100644 (file)
index 0000000..37371cb
--- /dev/null
@@ -0,0 +1,11 @@
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+#
+# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/e2e/protractor.conf.js b/dashboard/webapp-frontend/e2e/protractor.conf.js
new file mode 100644 (file)
index 0000000..3d5386c
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+  allScriptsTimeout: 11000,
+  specs: [
+    './src/**/*.e2e-spec.ts'
+  ],
+  capabilities: {
+    'browserName': 'chrome'
+  },
+  directConnect: true,
+  baseUrl: 'http://localhost:4200/',
+  framework: 'jasmine',
+  jasmineNodeOpts: {
+    showColors: true,
+    defaultTimeoutInterval: 30000,
+    print: function() {}
+  },
+  onPrepare() {
+    require('ts-node').register({
+      project: require('path').join(__dirname, './tsconfig.e2e.json')
+    });
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+  }
+};
diff --git a/dashboard/webapp-frontend/e2e/src/app.e2e-spec.ts b/dashboard/webapp-frontend/e2e/src/app.e2e-spec.ts
new file mode 100644 (file)
index 0000000..34de5c0
--- /dev/null
@@ -0,0 +1,42 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { AppPage } from './app.po';
+import { browser, logging } from 'protractor';
+
+describe('workspace-project App', () => {
+  let page: AppPage;
+
+  beforeEach(() => {
+    page = new AppPage();
+  });
+
+  it('should display welcome message', () => {
+    page.navigateTo();
+    expect(page.getTitleText()).toEqual('Welcome to dashApp!');
+  });
+
+  afterEach(async () => {
+    // Assert that there are no errors emitted from the browser
+    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
+    expect(logs).not.toContain(jasmine.objectContaining({
+      level: logging.Level.SEVERE,
+    }));
+  });
+});
diff --git a/dashboard/webapp-frontend/e2e/src/app.po.ts b/dashboard/webapp-frontend/e2e/src/app.po.ts
new file mode 100644 (file)
index 0000000..5e6f140
--- /dev/null
@@ -0,0 +1,30 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+  navigateTo() {
+    return browser.get('/') as Promise<any>;
+  }
+
+  getTitleText() {
+    return element(by.css('app-root h1')).getText() as Promise<string>;
+  }
+}
diff --git a/dashboard/webapp-frontend/e2e/tsconfig.e2e.json b/dashboard/webapp-frontend/e2e/tsconfig.e2e.json
new file mode 100644 (file)
index 0000000..a6dd622
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/app",
+    "module": "commonjs",
+    "target": "es5",
+    "types": [
+      "jasmine",
+      "jasminewd2",
+      "node"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/ng b/dashboard/webapp-frontend/ng
new file mode 100755 (executable)
index 0000000..a70d1b1
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# The MIT License
+#
+# Copyright (c) 2010-2019 Google LLC. http://angular.io/license
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+cd $(dirname $0)
+PATH="$PWD/node/":"$PWD":$PATH
+node_modules/@angular/cli/bin/ng "$@"
diff --git a/dashboard/webapp-frontend/npm b/dashboard/webapp-frontend/npm
new file mode 100755 (executable)
index 0000000..7b88bce
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# The MIT License
+#
+# Copyright (c) 2010-2019 Google LLC. http://angular.io/license
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+cd $(dirname $0)
+PATH="$PWD/node/":$PATH
+node "node/node_modules/npm/bin/npm-cli.js" "$@"
diff --git a/dashboard/webapp-frontend/package-lock.json b/dashboard/webapp-frontend/package-lock.json
new file mode 100644 (file)
index 0000000..b82a67c
--- /dev/null
@@ -0,0 +1,12647 @@
+{
+  "name": "dash-app",
+  "version": "0.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@angular-devkit/architect": {
+      "version": "0.803.8",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.8.tgz",
+      "integrity": "sha512-eenVmiFz7CXHkPT0sboaqHHQ48y43racZP648VMuvPJK5wHZhdwyfe5Whz4qnuEHca1fB0FbVDKP10okwVYfPQ==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.3.8",
+        "rxjs": "6.4.0"
+      },
+      "dependencies": {
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        }
+      }
+    },
+    "@angular-devkit/build-angular": {
+      "version": "0.803.8",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.8.tgz",
+      "integrity": "sha512-SKhUcTu2ZfjicUfyyef4SZwmLJoyRFPqA8wceJeqnrkGrkSoHHKgCeqL01fIBVOUaRoIsZQJPCG56YDTEjLKLQ==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/architect": "0.803.8",
+        "@angular-devkit/build-optimizer": "0.803.8",
+        "@angular-devkit/build-webpack": "0.803.8",
+        "@angular-devkit/core": "8.3.8",
+        "@babel/core": "7.5.5",
+        "@babel/preset-env": "7.5.5",
+        "@ngtools/webpack": "8.3.8",
+        "ajv": "6.10.2",
+        "autoprefixer": "9.6.1",
+        "browserslist": "4.6.6",
+        "cacache": "12.0.2",
+        "caniuse-lite": "1.0.30000989",
+        "circular-dependency-plugin": "5.2.0",
+        "clean-css": "4.2.1",
+        "copy-webpack-plugin": "5.0.4",
+        "core-js": "3.2.1",
+        "file-loader": "4.2.0",
+        "find-cache-dir": "3.0.0",
+        "glob": "7.1.4",
+        "istanbul-instrumenter-loader": "3.0.1",
+        "jest-worker": "24.9.0",
+        "karma-source-map-support": "1.4.0",
+        "less": "3.9.0",
+        "less-loader": "5.0.0",
+        "license-webpack-plugin": "2.1.2",
+        "loader-utils": "1.2.3",
+        "mini-css-extract-plugin": "0.8.0",
+        "minimatch": "3.0.4",
+        "open": "6.4.0",
+        "parse5": "4.0.0",
+        "postcss": "7.0.17",
+        "postcss-import": "12.0.1",
+        "postcss-loader": "3.0.0",
+        "raw-loader": "3.1.0",
+        "regenerator-runtime": "0.13.3",
+        "rxjs": "6.4.0",
+        "sass": "1.22.9",
+        "sass-loader": "7.2.0",
+        "semver": "6.3.0",
+        "source-map": "0.7.3",
+        "source-map-loader": "0.2.4",
+        "source-map-support": "0.5.13",
+        "speed-measure-webpack-plugin": "1.3.1",
+        "style-loader": "1.0.0",
+        "stylus": "0.54.5",
+        "stylus-loader": "3.0.2",
+        "terser": "4.1.4",
+        "terser-webpack-plugin": "1.4.1",
+        "tree-kill": "1.2.1",
+        "webpack": "4.39.2",
+        "webpack-dev-middleware": "3.7.0",
+        "webpack-dev-server": "3.8.0",
+        "webpack-merge": "4.2.1",
+        "webpack-sources": "1.4.3",
+        "webpack-subresource-integrity": "1.1.0-rc.6",
+        "worker-plugin": "3.2.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "core-js": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
+          "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
+          "dev": true
+        },
+        "glob": {
+          "version": "7.1.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+          "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "source-map-support": {
+          "version": "0.5.13",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+          "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+          "dev": true,
+          "requires": {
+            "buffer-from": "^1.0.0",
+            "source-map": "^0.6.0"
+          },
+          "dependencies": {
+            "source-map": {
+              "version": "0.6.1",
+              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+              "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+              "dev": true
+            }
+          }
+        }
+      }
+    },
+    "@angular-devkit/build-optimizer": {
+      "version": "0.803.8",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.8.tgz",
+      "integrity": "sha512-UiMxl1wI3acqIoRkC0WA0qpab+ni6SlCaB4UIwfD1H/FdzU80P04AIUuJS7StxjbwVkVtA05kcfgmqzP8yBMVg==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "1.2.3",
+        "source-map": "0.7.3",
+        "tslib": "1.10.0",
+        "typescript": "3.5.3",
+        "webpack-sources": "1.4.3"
+      }
+    },
+    "@angular-devkit/build-webpack": {
+      "version": "0.803.8",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.8.tgz",
+      "integrity": "sha512-NFG2suQG4w6uUf1bbduLi/sQw94J1nB3D9heNh5o6ov0Ps1fTA4YEDg3T0RQ8ljmfaLb+wHsxajztzOG/RRnZw==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/architect": "0.803.8",
+        "@angular-devkit/core": "8.3.8",
+        "rxjs": "6.4.0",
+        "webpack-merge": "4.2.1"
+      },
+      "dependencies": {
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        }
+      }
+    },
+    "@angular-devkit/core": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.8.tgz",
+      "integrity": "sha512-HwlMRr6qANwhOJS+5rGgQ2lmP4nj2C4cbUc0LlA09Cdbq0RnDquUFVqHF6h81FUKFW1D5qDehWYHNOVq8+gTkQ==",
+      "dev": true,
+      "requires": {
+        "ajv": "6.10.2",
+        "fast-json-stable-stringify": "2.0.0",
+        "magic-string": "0.25.3",
+        "rxjs": "6.4.0",
+        "source-map": "0.7.3"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        }
+      }
+    },
+    "@angular-devkit/schematics": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.8.tgz",
+      "integrity": "sha512-1KnluRj86QO6fDE++iNbUHq1nNHpz0ZQDs/siy+tDtenO5TxAO/vegHYNKvsIcMMUF9z2kHA0qwUbq5oN8K85g==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.3.8",
+        "rxjs": "6.4.0"
+      },
+      "dependencies": {
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        }
+      }
+    },
+    "@angular/animations": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.2.9.tgz",
+      "integrity": "sha512-l30AF0d9P5okTPM1wieUHgcnDyGSNvyaBcxXSOkT790wAP2v5zs7VrKq9Lm+ICu4Nkx07KrOr5XLUHhqsg3VXA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/cdk": {
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.3.7.tgz",
+      "integrity": "sha512-xbXxhHHKGkVuW6K7pzPmvpJXIwpl0ykBnvA2g+/7Sgy5Pd35wCC+UtHD9RYczDM/mkygNxMQtagyCErwFnDtQA==",
+      "requires": {
+        "parse5": "^5.0.0",
+        "tslib": "^1.7.1"
+      },
+      "dependencies": {
+        "parse5": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
+          "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
+          "optional": true
+        }
+      }
+    },
+    "@angular/cli": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.8.tgz",
+      "integrity": "sha512-JmumKB21XKyQwe3fSeaaEGTWuv39mtrNQ4CWIXzLKY+oWdpBy+G82JRjXM3OMLmKGrmxiAjTc6kP0oRYaq25JA==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/architect": "0.803.8",
+        "@angular-devkit/core": "8.3.8",
+        "@angular-devkit/schematics": "8.3.8",
+        "@schematics/angular": "8.3.8",
+        "@schematics/update": "0.803.8",
+        "@yarnpkg/lockfile": "1.1.0",
+        "ansi-colors": "4.1.1",
+        "debug": "^4.1.1",
+        "ini": "1.3.5",
+        "inquirer": "6.5.1",
+        "npm-package-arg": "6.1.0",
+        "npm-pick-manifest": "3.0.2",
+        "open": "6.4.0",
+        "pacote": "9.5.5",
+        "read-package-tree": "5.3.1",
+        "semver": "6.3.0",
+        "symbol-observable": "1.2.0",
+        "universal-analytics": "^0.4.20",
+        "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "ansi-colors": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+          "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+          "dev": true
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "@angular/common": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.2.9.tgz",
+      "integrity": "sha512-76WDU1USlI5vAzqCJ3gxCQGuu57aJEggNk/xoWmQEXipiFTFBh2wSKn/dE6Txr/q3COTPIcrmb9OCeal5kQPIA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/compiler": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.9.tgz",
+      "integrity": "sha512-oQho19DnOhEDNerCOGuGK95tcZ2oy4dSA5SykJmmniRnZzPM2++bJD32qJehXHy1K+3hv2zN9x7HPhqT3ljT6g==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/compiler-cli": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.2.9.tgz",
+      "integrity": "sha512-tqGBKPf3SRYNEGGJbmjom//U/eAjnecDhGUw6o+VkYE/wxYd9pPcLmcEwwyXBpIPJAsN8RsjTikPuH0gcNE8bw==",
+      "dev": true,
+      "requires": {
+        "canonical-path": "1.0.0",
+        "chokidar": "^2.1.1",
+        "convert-source-map": "^1.5.1",
+        "dependency-graph": "^0.7.2",
+        "magic-string": "^0.25.0",
+        "minimist": "^1.2.0",
+        "reflect-metadata": "^0.1.2",
+        "source-map": "^0.6.1",
+        "tslib": "^1.9.0",
+        "yargs": "13.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
+        },
+        "chokidar": {
+          "version": "2.1.8",
+          "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+          "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+          "dev": true,
+          "requires": {
+            "anymatch": "^2.0.0",
+            "async-each": "^1.0.1",
+            "braces": "^2.3.2",
+            "fsevents": "^1.2.7",
+            "glob-parent": "^3.1.0",
+            "inherits": "^2.0.3",
+            "is-binary-path": "^1.0.0",
+            "is-glob": "^4.0.0",
+            "normalize-path": "^3.0.0",
+            "path-is-absolute": "^1.0.0",
+            "readdirp": "^2.2.1",
+            "upath": "^1.1.1"
+          }
+        },
+        "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
+        },
+        "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
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        },
+        "normalize-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+          "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+          "dev": true
+        },
+        "require-main-filename": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+          "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "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"
+          }
+        },
+        "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"
+          }
+        },
+        "upath": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+          "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+          "dev": true
+        },
+        "yargs": {
+          "version": "13.1.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz",
+          "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==",
+          "dev": true,
+          "requires": {
+            "cliui": "^4.0.0",
+            "find-up": "^3.0.0",
+            "get-caller-file": "^2.0.1",
+            "os-locale": "^3.1.0",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^3.0.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^13.0.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "13.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+          "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+          "dev": true,
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
+    "@angular/core": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.2.9.tgz",
+      "integrity": "sha512-GpHAuLOlN9iioELCQBmAsjETTUCyFgVUI3LXwh3e63jnpd+ZuuZcZbjfTYhtgYVNMetn7cVEO6p88eb7qvpUWQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/flex-layout": {
+      "version": "7.0.0-beta.19",
+      "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-7.0.0-beta.19.tgz",
+      "integrity": "sha512-MXq+zZ6/s5/+GsL9fZ42mKL0LjZ/+L0sVU5FaQuSAJ57soLl5QAGWvdxVmROtqcHd3Htp35R49nKSZBJ0nfAjg==",
+      "requires": {
+        "tslib": "^1.7.1"
+      }
+    },
+    "@angular/forms": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.2.9.tgz",
+      "integrity": "sha512-kAdBuApC9PPOdPI8BmNhxCraAkXGbX/PkVan8pQ5xdumvgGqvVjbJvLaUSbJROPtgCRlQyiEDrHFd4gk/WU76A==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/language-service": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.2.9.tgz",
+      "integrity": "sha512-F6ReN0cToHIkCjEM2ECkBxCTsvFjVae8FpIr3Fz8IHZHOOYcS5mx/BWdEO7odI5/tQKl+cCWol7NjvJYV0zolg==",
+      "dev": true
+    },
+    "@angular/material": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/material/-/material-7.2.2.tgz",
+      "integrity": "sha512-HTtDhK5XkDvP6GPg4WTTa0HbeeagTfVRooTfw0TA+IuTAMBhXt5h1yzuGpFyMap8/PUVZN1D04g2CLhBSzoDxg==",
+      "requires": {
+        "tslib": "^1.7.1"
+      }
+    },
+    "@angular/platform-browser": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.9.tgz",
+      "integrity": "sha512-k3aNZy0OTqGn7HlHHV52QF6ZAP/VlQhWGD2u5e1dWIWMq39kdkdSCNu5tiuAf5hIzMBiSQ0tjnuVWA4MuDBYIQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/platform-browser-dynamic": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.9.tgz",
+      "integrity": "sha512-GbE4TUy4n/a8yp8fLWwdG/QnjUPZZ8VufItZ7GvOpoyknzegvka111dLctvMoPzSAsrKyShL6cryuyDC5PShUA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "@angular/router": {
+      "version": "8.2.9",
+      "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.9.tgz",
+      "integrity": "sha512-4P60CWNB/jxGjDBEuYN0Jobt76QlebAQeFBTDswRVwRlq/WJT4QhL3a8AVIRsHn9bQII0LUt/ZQBBPxn7h9lSA==",
+      "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",
+      "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.0.0"
+      }
+    },
+    "@babel/core": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz",
+      "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.5.5",
+        "@babel/generator": "^7.5.5",
+        "@babel/helpers": "^7.5.5",
+        "@babel/parser": "^7.5.5",
+        "@babel/template": "^7.4.4",
+        "@babel/traverse": "^7.5.5",
+        "@babel/types": "^7.5.5",
+        "convert-source-map": "^1.1.0",
+        "debug": "^4.1.0",
+        "json5": "^2.1.0",
+        "lodash": "^4.17.13",
+        "resolve": "^1.3.2",
+        "semver": "^5.4.1",
+        "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "@babel/code-frame": {
+          "version": "7.5.5",
+          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+          "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
+          "dev": true,
+          "requires": {
+            "@babel/highlight": "^7.0.0"
+          }
+        },
+        "@babel/generator": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.3.tgz",
+          "integrity": "sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==",
+          "dev": true,
+          "requires": {
+            "@babel/types": "^7.6.3",
+            "jsesc": "^2.5.1",
+            "lodash": "^4.17.13",
+            "source-map": "^0.6.1"
+          },
+          "dependencies": {
+            "source-map": {
+              "version": "0.6.1",
+              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+              "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+              "dev": true
+            }
+          }
+        },
+        "@babel/parser": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.3.tgz",
+          "integrity": "sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==",
+          "dev": true
+        },
+        "@babel/traverse": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz",
+          "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.5.5",
+            "@babel/generator": "^7.6.3",
+            "@babel/helper-function-name": "^7.1.0",
+            "@babel/helper-split-export-declaration": "^7.4.4",
+            "@babel/parser": "^7.6.3",
+            "@babel/types": "^7.6.3",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          }
+        },
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "json5": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz",
+          "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "resolve": {
+          "version": "1.12.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+          "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+          "dev": true,
+          "requires": {
+            "path-parse": "^1.0.6"
+          }
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "@babel/generator": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz",
+      "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.4.4",
+        "jsesc": "^2.5.1",
+        "lodash": "^4.17.11",
+        "source-map": "^0.5.0",
+        "trim-right": "^1.0.1"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "2.5.2",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+          "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-annotate-as-pure": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz",
+      "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-builder-binary-assignment-operator-visitor": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz",
+      "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-explode-assignable-expression": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-call-delegate": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz",
+      "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-hoist-variables": "^7.4.4",
+        "@babel/traverse": "^7.4.4",
+        "@babel/types": "^7.4.4"
+      }
+    },
+    "@babel/helper-define-map": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz",
+      "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.1.0",
+        "@babel/types": "^7.5.5",
+        "lodash": "^4.17.13"
+      },
+      "dependencies": {
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-explode-assignable-expression": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz",
+      "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==",
+      "dev": true,
+      "requires": {
+        "@babel/traverse": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-function-name": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
+      "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.0.0",
+        "@babel/template": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-get-function-arity": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
+      "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-hoist-variables": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz",
+      "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.4.4"
+      }
+    },
+    "@babel/helper-member-expression-to-functions": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz",
+      "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.5.5"
+      },
+      "dependencies": {
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-module-imports": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
+      "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-module-transforms": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz",
+      "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.0.0",
+        "@babel/helper-simple-access": "^7.1.0",
+        "@babel/helper-split-export-declaration": "^7.4.4",
+        "@babel/template": "^7.4.4",
+        "@babel/types": "^7.5.5",
+        "lodash": "^4.17.13"
+      },
+      "dependencies": {
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-optimise-call-expression": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
+      "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-plugin-utils": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
+      "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
+      "dev": true
+    },
+    "@babel/helper-regex": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz",
+      "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.13"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-remap-async-to-generator": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz",
+      "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.0.0",
+        "@babel/helper-wrap-function": "^7.1.0",
+        "@babel/template": "^7.1.0",
+        "@babel/traverse": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-replace-supers": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz",
+      "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-member-expression-to-functions": "^7.5.5",
+        "@babel/helper-optimise-call-expression": "^7.0.0",
+        "@babel/traverse": "^7.5.5",
+        "@babel/types": "^7.5.5"
+      },
+      "dependencies": {
+        "@babel/code-frame": {
+          "version": "7.5.5",
+          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+          "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
+          "dev": true,
+          "requires": {
+            "@babel/highlight": "^7.0.0"
+          }
+        },
+        "@babel/generator": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.3.tgz",
+          "integrity": "sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==",
+          "dev": true,
+          "requires": {
+            "@babel/types": "^7.6.3",
+            "jsesc": "^2.5.1",
+            "lodash": "^4.17.13",
+            "source-map": "^0.6.1"
+          }
+        },
+        "@babel/parser": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.3.tgz",
+          "integrity": "sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==",
+          "dev": true
+        },
+        "@babel/traverse": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz",
+          "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.5.5",
+            "@babel/generator": "^7.6.3",
+            "@babel/helper-function-name": "^7.1.0",
+            "@babel/helper-split-export-declaration": "^7.4.4",
+            "@babel/parser": "^7.6.3",
+            "@babel/types": "^7.6.3",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          }
+        },
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-simple-access": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz",
+      "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==",
+      "dev": true,
+      "requires": {
+        "@babel/template": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "@babel/helper-split-export-declaration": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
+      "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.4.4"
+      }
+    },
+    "@babel/helper-wrap-function": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz",
+      "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.1.0",
+        "@babel/template": "^7.1.0",
+        "@babel/traverse": "^7.1.0",
+        "@babel/types": "^7.2.0"
+      }
+    },
+    "@babel/helpers": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz",
+      "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==",
+      "dev": true,
+      "requires": {
+        "@babel/template": "^7.6.0",
+        "@babel/traverse": "^7.6.2",
+        "@babel/types": "^7.6.0"
+      },
+      "dependencies": {
+        "@babel/generator": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.3.tgz",
+          "integrity": "sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==",
+          "dev": true,
+          "requires": {
+            "@babel/types": "^7.6.3",
+            "jsesc": "^2.5.1",
+            "lodash": "^4.17.13",
+            "source-map": "^0.6.1"
+          }
+        },
+        "@babel/parser": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.3.tgz",
+          "integrity": "sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==",
+          "dev": true
+        },
+        "@babel/template": {
+          "version": "7.6.0",
+          "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
+          "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.0.0",
+            "@babel/parser": "^7.6.0",
+            "@babel/types": "^7.6.0"
+          }
+        },
+        "@babel/traverse": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz",
+          "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.5.5",
+            "@babel/generator": "^7.6.3",
+            "@babel/helper-function-name": "^7.1.0",
+            "@babel/helper-split-export-declaration": "^7.4.4",
+            "@babel/parser": "^7.6.3",
+            "@babel/types": "^7.6.3",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          },
+          "dependencies": {
+            "@babel/code-frame": {
+              "version": "7.5.5",
+              "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+              "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
+              "dev": true,
+              "requires": {
+                "@babel/highlight": "^7.0.0"
+              }
+            }
+          }
+        },
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
+      "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.0",
+        "esutils": "^2.0.2",
+        "js-tokens": "^4.0.0"
+      },
+      "dependencies": {
+        "js-tokens": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+          "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/parser": {
+      "version": "7.4.5",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz",
+      "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==",
+      "dev": true
+    },
+    "@babel/plugin-proposal-async-generator-functions": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz",
+      "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-remap-async-to-generator": "^7.1.0",
+        "@babel/plugin-syntax-async-generators": "^7.2.0"
+      }
+    },
+    "@babel/plugin-proposal-dynamic-import": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz",
+      "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-dynamic-import": "^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",
+      "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-json-strings": "^7.2.0"
+      }
+    },
+    "@babel/plugin-proposal-object-rest-spread": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz",
+      "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-object-rest-spread": "^7.2.0"
+      }
+    },
+    "@babel/plugin-proposal-optional-catch-binding": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz",
+      "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.2.0"
+      }
+    },
+    "@babel/plugin-proposal-unicode-property-regex": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz",
+      "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-regex": "^7.4.4",
+        "regexpu-core": "^4.6.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        },
+        "regexpu-core": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz",
+          "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==",
+          "dev": true,
+          "requires": {
+            "regenerate": "^1.4.0",
+            "regenerate-unicode-properties": "^8.1.0",
+            "regjsgen": "^0.5.0",
+            "regjsparser": "^0.6.0",
+            "unicode-match-property-ecmascript": "^1.0.4",
+            "unicode-match-property-value-ecmascript": "^1.1.0"
+          }
+        },
+        "regjsgen": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+          "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
+          "dev": true
+        },
+        "regjsparser": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+          "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+          "dev": true,
+          "requires": {
+            "jsesc": "~0.5.0"
+          }
+        }
+      }
+    },
+    "@babel/plugin-syntax-async-generators": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz",
+      "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-syntax-dynamic-import": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz",
+      "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-syntax-json-strings": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz",
+      "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-syntax-object-rest-spread": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
+      "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-syntax-optional-catch-binding": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz",
+      "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-arrow-functions": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
+      "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-async-to-generator": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz",
+      "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.0.0",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-remap-async-to-generator": "^7.1.0"
+      }
+    },
+    "@babel/plugin-transform-block-scoped-functions": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz",
+      "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-block-scoping": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz",
+      "integrity": "sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "lodash": "^4.17.13"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/plugin-transform-classes": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz",
+      "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.0.0",
+        "@babel/helper-define-map": "^7.5.5",
+        "@babel/helper-function-name": "^7.1.0",
+        "@babel/helper-optimise-call-expression": "^7.0.0",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-replace-supers": "^7.5.5",
+        "@babel/helper-split-export-declaration": "^7.4.4",
+        "globals": "^11.1.0"
+      }
+    },
+    "@babel/plugin-transform-computed-properties": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz",
+      "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-destructuring": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz",
+      "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-dotall-regex": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz",
+      "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-regex": "^7.4.4",
+        "regexpu-core": "^4.6.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        },
+        "regexpu-core": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz",
+          "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==",
+          "dev": true,
+          "requires": {
+            "regenerate": "^1.4.0",
+            "regenerate-unicode-properties": "^8.1.0",
+            "regjsgen": "^0.5.0",
+            "regjsparser": "^0.6.0",
+            "unicode-match-property-ecmascript": "^1.0.4",
+            "unicode-match-property-value-ecmascript": "^1.1.0"
+          }
+        },
+        "regjsgen": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+          "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
+          "dev": true
+        },
+        "regjsparser": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+          "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+          "dev": true,
+          "requires": {
+            "jsesc": "~0.5.0"
+          }
+        }
+      }
+    },
+    "@babel/plugin-transform-duplicate-keys": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz",
+      "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-exponentiation-operator": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz",
+      "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0",
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-for-of": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz",
+      "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-function-name": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz",
+      "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.1.0",
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-literals": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz",
+      "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-member-expression-literals": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz",
+      "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-modules-amd": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz",
+      "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-transforms": "^7.1.0",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-commonjs": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz",
+      "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-transforms": "^7.4.4",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-simple-access": "^7.1.0",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-systemjs": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz",
+      "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-hoist-variables": "^7.4.4",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-umd": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz",
+      "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-transforms": "^7.1.0",
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-named-capturing-groups-regex": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz",
+      "integrity": "sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==",
+      "dev": true,
+      "requires": {
+        "regexpu-core": "^4.6.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        },
+        "regexpu-core": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz",
+          "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==",
+          "dev": true,
+          "requires": {
+            "regenerate": "^1.4.0",
+            "regenerate-unicode-properties": "^8.1.0",
+            "regjsgen": "^0.5.0",
+            "regjsparser": "^0.6.0",
+            "unicode-match-property-ecmascript": "^1.0.4",
+            "unicode-match-property-value-ecmascript": "^1.1.0"
+          }
+        },
+        "regjsgen": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+          "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
+          "dev": true
+        },
+        "regjsparser": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+          "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+          "dev": true,
+          "requires": {
+            "jsesc": "~0.5.0"
+          }
+        }
+      }
+    },
+    "@babel/plugin-transform-new-target": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz",
+      "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-object-super": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz",
+      "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-replace-supers": "^7.5.5"
+      }
+    },
+    "@babel/plugin-transform-parameters": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz",
+      "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-call-delegate": "^7.4.4",
+        "@babel/helper-get-function-arity": "^7.0.0",
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-property-literals": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz",
+      "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-regenerator": {
+      "version": "7.4.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz",
+      "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==",
+      "dev": true,
+      "requires": {
+        "regenerator-transform": "^0.14.0"
+      }
+    },
+    "@babel/plugin-transform-reserved-words": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz",
+      "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-shorthand-properties": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
+      "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-spread": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz",
+      "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-sticky-regex": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz",
+      "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-regex": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-template-literals": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz",
+      "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.0.0",
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-typeof-symbol": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz",
+      "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0"
+      }
+    },
+    "@babel/plugin-transform-unicode-regex": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz",
+      "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/helper-regex": "^7.4.4",
+        "regexpu-core": "^4.6.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        },
+        "regexpu-core": {
+          "version": "4.6.0",
+          "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz",
+          "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==",
+          "dev": true,
+          "requires": {
+            "regenerate": "^1.4.0",
+            "regenerate-unicode-properties": "^8.1.0",
+            "regjsgen": "^0.5.0",
+            "regjsparser": "^0.6.0",
+            "unicode-match-property-ecmascript": "^1.0.4",
+            "unicode-match-property-value-ecmascript": "^1.1.0"
+          }
+        },
+        "regjsgen": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+          "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
+          "dev": true
+        },
+        "regjsparser": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+          "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+          "dev": true,
+          "requires": {
+            "jsesc": "~0.5.0"
+          }
+        }
+      }
+    },
+    "@babel/preset-env": {
+      "version": "7.5.5",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz",
+      "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.0.0",
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-proposal-async-generator-functions": "^7.2.0",
+        "@babel/plugin-proposal-dynamic-import": "^7.5.0",
+        "@babel/plugin-proposal-json-strings": "^7.2.0",
+        "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
+        "@babel/plugin-proposal-optional-catch-binding": "^7.2.0",
+        "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
+        "@babel/plugin-syntax-async-generators": "^7.2.0",
+        "@babel/plugin-syntax-dynamic-import": "^7.2.0",
+        "@babel/plugin-syntax-json-strings": "^7.2.0",
+        "@babel/plugin-syntax-object-rest-spread": "^7.2.0",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.2.0",
+        "@babel/plugin-transform-arrow-functions": "^7.2.0",
+        "@babel/plugin-transform-async-to-generator": "^7.5.0",
+        "@babel/plugin-transform-block-scoped-functions": "^7.2.0",
+        "@babel/plugin-transform-block-scoping": "^7.5.5",
+        "@babel/plugin-transform-classes": "^7.5.5",
+        "@babel/plugin-transform-computed-properties": "^7.2.0",
+        "@babel/plugin-transform-destructuring": "^7.5.0",
+        "@babel/plugin-transform-dotall-regex": "^7.4.4",
+        "@babel/plugin-transform-duplicate-keys": "^7.5.0",
+        "@babel/plugin-transform-exponentiation-operator": "^7.2.0",
+        "@babel/plugin-transform-for-of": "^7.4.4",
+        "@babel/plugin-transform-function-name": "^7.4.4",
+        "@babel/plugin-transform-literals": "^7.2.0",
+        "@babel/plugin-transform-member-expression-literals": "^7.2.0",
+        "@babel/plugin-transform-modules-amd": "^7.5.0",
+        "@babel/plugin-transform-modules-commonjs": "^7.5.0",
+        "@babel/plugin-transform-modules-systemjs": "^7.5.0",
+        "@babel/plugin-transform-modules-umd": "^7.2.0",
+        "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5",
+        "@babel/plugin-transform-new-target": "^7.4.4",
+        "@babel/plugin-transform-object-super": "^7.5.5",
+        "@babel/plugin-transform-parameters": "^7.4.4",
+        "@babel/plugin-transform-property-literals": "^7.2.0",
+        "@babel/plugin-transform-regenerator": "^7.4.5",
+        "@babel/plugin-transform-reserved-words": "^7.2.0",
+        "@babel/plugin-transform-shorthand-properties": "^7.2.0",
+        "@babel/plugin-transform-spread": "^7.2.0",
+        "@babel/plugin-transform-sticky-regex": "^7.2.0",
+        "@babel/plugin-transform-template-literals": "^7.4.4",
+        "@babel/plugin-transform-typeof-symbol": "^7.2.0",
+        "@babel/plugin-transform-unicode-regex": "^7.4.4",
+        "@babel/types": "^7.5.5",
+        "browserslist": "^4.6.0",
+        "core-js-compat": "^3.1.1",
+        "invariant": "^2.2.2",
+        "js-levenshtein": "^1.1.3",
+        "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "@babel/types": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
+          "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/template": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
+      "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@babel/parser": "^7.4.4",
+        "@babel/types": "^7.4.4"
+      }
+    },
+    "@babel/traverse": {
+      "version": "7.4.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz",
+      "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@babel/generator": "^7.4.4",
+        "@babel/helper-function-name": "^7.1.0",
+        "@babel/helper-split-export-declaration": "^7.4.4",
+        "@babel/parser": "^7.4.5",
+        "@babel/types": "^7.4.4",
+        "debug": "^4.1.0",
+        "globals": "^11.1.0",
+        "lodash": "^4.17.11"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "globals": {
+          "version": "11.12.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+          "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "@babel/types": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
+      "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2",
+        "lodash": "^4.17.11",
+        "to-fast-properties": "^2.0.0"
+      },
+      "dependencies": {
+        "to-fast-properties": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+          "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+          "dev": true
+        }
+      }
+    },
+    "@fortawesome/fontawesome-free": {
+      "version": "5.9.0",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.9.0.tgz",
+      "integrity": "sha512-g795BBEzM/Hq2SYNPm/NQTIp3IWd4eXSH0ds87Na2jnrAUFX3wkyZAI4Gwj9DOaWMuz2/01i8oWI7P7T/XLkhg=="
+    },
+    "@kubernetes/client-node": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.10.3.tgz",
+      "integrity": "sha512-mw+1zdKfMW4QN2ns82SKFhAvqC4SVUAiItto4oFg3Me+a510h3h9N5O7ad6m4efAmlQBlMc6Y5FHz70dAwuiMg==",
+      "requires": {
+        "@types/js-yaml": "^3.12.1",
+        "@types/node": "^10.12.0",
+        "@types/request": "^2.47.1",
+        "@types/underscore": "^1.8.9",
+        "@types/ws": "^6.0.1",
+        "byline": "^5.0.0",
+        "execa": "1.0.0",
+        "isomorphic-ws": "^4.0.1",
+        "js-yaml": "^3.13.1",
+        "jsonpath-plus": "^0.19.0",
+        "openid-client": "2.5.0",
+        "request": "^2.88.0",
+        "shelljs": "^0.8.2",
+        "tslib": "^1.9.3",
+        "underscore": "^1.9.1",
+        "ws": "^6.1.0"
+      },
+      "dependencies": {
+        "@types/node": {
+          "version": "10.14.21",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.21.tgz",
+          "integrity": "sha512-nuFlRdBiqbF+PJIEVxm2jLFcQWN7q7iWEJGsBV4n7v1dbI9qXB8im2pMMKMCUZe092sQb5SQft2DHfuQGK5hqQ=="
+        },
+        "cross-spawn": {
+          "version": "6.0.5",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+          "requires": {
+            "nice-try": "^1.0.4",
+            "path-key": "^2.0.1",
+            "semver": "^5.5.0",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        },
+        "execa": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+          "requires": {
+            "cross-spawn": "^6.0.0",
+            "get-stream": "^4.0.0",
+            "is-stream": "^1.1.0",
+            "npm-run-path": "^2.0.0",
+            "p-finally": "^1.0.0",
+            "signal-exit": "^3.0.0",
+            "strip-eof": "^1.0.0"
+          }
+        },
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        },
+        "pump": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+          "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+          "requires": {
+            "end-of-stream": "^1.1.0",
+            "once": "^1.3.1"
+          }
+        },
+        "ws": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+          "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+          "requires": {
+            "async-limiter": "~1.0.0"
+          }
+        }
+      }
+    },
+    "@material/animation": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@material/animation/-/animation-1.0.0.tgz",
+      "integrity": "sha512-Ed5/vggn6ZhSJ87yn3ZS1d826VJNFz73jHF2bSsgRtHDoB8KCuOwQMfdgAgDa4lKDF6CDIPCKBZPKrs2ubehdw==",
+      "requires": {
+        "tslib": "^1.9.3"
+      }
+    },
+    "@material/base": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@material/base/-/base-1.0.0.tgz",
+      "integrity": "sha512-5dxFp46x5FA+Epg6YHLzN+5zRt9S2wR84UdvVAEJ1egea94m9UHUg7y9tAnNSN16aexRSywmzyLwPr+i8PGEYA==",
+      "requires": {
+        "tslib": "^1.9.3"
+      }
+    },
+    "@material/dom": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@material/dom/-/dom-1.1.0.tgz",
+      "integrity": "sha512-+HWW38ZaM2UBPu4+7QCusLDSf4tFT31rsEXHkTkxYSg/QpDivfPx6YDz4OmYtafmhPR1d1YjqB3MYysUHdodyw==",
+      "requires": {
+        "tslib": "^1.9.3"
+      }
+    },
+    "@material/feature-targeting": {
+      "version": "0.44.1",
+      "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-0.44.1.tgz",
+      "integrity": "sha512-90cc7njn4aHbH9UxY8qgZth1W5JgOgcEdWdubH1t7sFkwqFxS5g3zgxSBt46TygFBVIXNZNq35Xmg80wgqO7Pg=="
+    },
+    "@material/radio": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/@material/radio/-/radio-2.3.0.tgz",
+      "integrity": "sha512-l22cFvA/cZj/tsyDJVk1xayXGUTFFXp8yFe4SuGRW00/hNgGNF44rOSjN5H41iPU3oD1AreQL8r43fWW4eMH8w==",
+      "requires": {
+        "@material/animation": "^1.0.0",
+        "@material/base": "^1.0.0",
+        "@material/feature-targeting": "^0.44.1",
+        "@material/ripple": "^2.3.0",
+        "@material/theme": "^1.1.0",
+        "tslib": "^1.9.3"
+      }
+    },
+    "@material/ripple": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-2.3.0.tgz",
+      "integrity": "sha512-ejXR0nstERofFhssRyFlwOLgebwm2uGbarHtWZ2/+7QY2Th/Z1wOqNb2h/WRoShsJXK11RUsochb6BJrg30u7w==",
+      "requires": {
+        "@material/animation": "^1.0.0",
+        "@material/base": "^1.0.0",
+        "@material/dom": "^1.1.0",
+        "@material/feature-targeting": "^0.44.1",
+        "@material/theme": "^1.1.0",
+        "tslib": "^1.9.3"
+      }
+    },
+    "@material/theme": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@material/theme/-/theme-1.1.0.tgz",
+      "integrity": "sha512-YYUV9Rhbx4r/EMb/zoOYJUWjhXChNaLlH1rqt3vpNVyxRcxGqoVMGp5u1XALBCFiD9dACPKLIkKyRYa928nmPQ==",
+      "requires": {
+        "@material/feature-targeting": "^0.44.1"
+      }
+    },
+    "@ngtools/webpack": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.8.tgz",
+      "integrity": "sha512-jLN4/Abue+Ro/K2SF0TpHOXnFHGuaHQ4aL6QG++moZXavBxRdc2E+PDjtuaMaS1llLHs5C5GX+Ve9ueEFhWoeQ==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.3.8",
+        "enhanced-resolve": "4.1.0",
+        "rxjs": "6.4.0",
+        "tree-kill": "1.2.1",
+        "webpack-sources": "1.4.3"
+      },
+      "dependencies": {
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        }
+      }
+    },
+    "@schematics/angular": {
+      "version": "8.3.8",
+      "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.8.tgz",
+      "integrity": "sha512-TOueA7gfQ5P7iDS593EBtLtqqgl6Pvg6snWqbMaW74VUxd5euAuPMPKkoJPddKF+cWjGYZgF09tp7G0kbuLGqw==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.3.8",
+        "@angular-devkit/schematics": "8.3.8"
+      }
+    },
+    "@schematics/update": {
+      "version": "0.803.8",
+      "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.8.tgz",
+      "integrity": "sha512-1gf59IbRPsOeoo997B+tHJ1YlCIHUCUqiYDCgxcoV+4EjYtCLNC/9cWIehZ9adqan9o6o1ma7MJznVNzvFyCSQ==",
+      "dev": true,
+      "requires": {
+        "@angular-devkit/core": "8.3.8",
+        "@angular-devkit/schematics": "8.3.8",
+        "@yarnpkg/lockfile": "1.1.0",
+        "ini": "1.3.5",
+        "pacote": "9.5.5",
+        "rxjs": "6.4.0",
+        "semver": "6.3.0",
+        "semver-intersect": "1.4.0"
+      },
+      "dependencies": {
+        "rxjs": {
+          "version": "6.4.0",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
+          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "@sindresorhus/is": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
+      "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
+    },
+    "@types/caseless": {
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
+      "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w=="
+    },
+    "@types/chart.js": {
+      "version": "2.7.54",
+      "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.54.tgz",
+      "integrity": "sha512-BxIUR4mfk0zOqOPEu4gxLP5herra6INQLyFmgVE6JVRNNB+r36g2cd67nDUEEdD/EShZvaR33xausxOGv1+nbw=="
+    },
+    "@types/events": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+      "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+      "dev": true
+    },
+    "@types/glob": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+      "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+      "dev": true,
+      "requires": {
+        "@types/events": "*",
+        "@types/minimatch": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/jasmine": {
+      "version": "2.8.16",
+      "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.16.tgz",
+      "integrity": "sha512-056oRlBBp7MDzr+HoU5su099s/s7wjZ3KcHxLfv+Byqb9MwdLUvsfLgw1VS97hsh3ddxSPyQu+olHMnoVTUY6g==",
+      "dev": true
+    },
+    "@types/jasminewd2": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.6.tgz",
+      "integrity": "sha512-2ZOKrxb8bKRmP/po5ObYnRDgFE4i+lQiEB27bAMmtMWLgJSqlIDqlLx6S0IRorpOmOPRQ6O80NujTmQAtBkeNw==",
+      "dev": true,
+      "requires": {
+        "@types/jasmine": "*"
+      }
+    },
+    "@types/js-yaml": {
+      "version": "3.12.1",
+      "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz",
+      "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA=="
+    },
+    "@types/minimatch": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+      "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "8.9.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
+      "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ=="
+    },
+    "@types/q": {
+      "version": "0.0.32",
+      "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
+      "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=",
+      "dev": true
+    },
+    "@types/request": {
+      "version": "2.48.3",
+      "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz",
+      "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==",
+      "requires": {
+        "@types/caseless": "*",
+        "@types/node": "*",
+        "@types/tough-cookie": "*",
+        "form-data": "^2.5.0"
+      },
+      "dependencies": {
+        "form-data": {
+          "version": "2.5.1",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
+          "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.6",
+            "mime-types": "^2.1.12"
+          }
+        }
+      }
+    },
+    "@types/selenium-webdriver": {
+      "version": "3.0.14",
+      "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.14.tgz",
+      "integrity": "sha512-4GbNCDs98uHCT/OMv40qQC/OpoPbYn9XdXeTiFwHBBFO6eJhYEPUu2zDKirXSbHlvDV8oZ9l8EQ+HrEx/YS9DQ==",
+      "dev": true
+    },
+    "@types/source-list-map": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
+      "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
+      "dev": true
+    },
+    "@types/tough-cookie": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz",
+      "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg=="
+    },
+    "@types/underscore": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.9.3.tgz",
+      "integrity": "sha512-SwbHKB2DPIDlvYqtK5O+0LFtZAyrUSw4c0q+HWwmH1Ve3KMQ0/5PlV3RX97+3dP7yMrnNQ8/bCWWvQpPl03Mug=="
+    },
+    "@types/webpack-sources": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz",
+      "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/source-list-map": "*",
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "@types/ws": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.3.tgz",
+      "integrity": "sha512-yBTM0P05Tx9iXGq00BbJPo37ox68R5vaGTXivs6RGh/BQ6QP5zqZDGWdAO6JbRE/iR1l80xeGAwCQS2nMV9S/w==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@webassemblyjs/ast": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
+      "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/helper-module-context": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/wast-parser": "1.8.5"
+      }
+    },
+    "@webassemblyjs/floating-point-hex-parser": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz",
+      "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-api-error": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz",
+      "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-buffer": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz",
+      "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-code-frame": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz",
+      "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/wast-printer": "1.8.5"
+      }
+    },
+    "@webassemblyjs/helper-fsm": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz",
+      "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-module-context": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
+      "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "mamacro": "^0.0.3"
+      }
+    },
+    "@webassemblyjs/helper-wasm-bytecode": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz",
+      "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-wasm-section": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz",
+      "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-buffer": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/wasm-gen": "1.8.5"
+      }
+    },
+    "@webassemblyjs/ieee754": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz",
+      "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
+      "dev": true,
+      "requires": {
+        "@xtuc/ieee754": "^1.2.0"
+      }
+    },
+    "@webassemblyjs/leb128": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz",
+      "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==",
+      "dev": true,
+      "requires": {
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/utf8": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz",
+      "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==",
+      "dev": true
+    },
+    "@webassemblyjs/wasm-edit": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz",
+      "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-buffer": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/helper-wasm-section": "1.8.5",
+        "@webassemblyjs/wasm-gen": "1.8.5",
+        "@webassemblyjs/wasm-opt": "1.8.5",
+        "@webassemblyjs/wasm-parser": "1.8.5",
+        "@webassemblyjs/wast-printer": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wasm-gen": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz",
+      "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/ieee754": "1.8.5",
+        "@webassemblyjs/leb128": "1.8.5",
+        "@webassemblyjs/utf8": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wasm-opt": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz",
+      "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-buffer": "1.8.5",
+        "@webassemblyjs/wasm-gen": "1.8.5",
+        "@webassemblyjs/wasm-parser": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wasm-parser": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz",
+      "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-api-error": "1.8.5",
+        "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+        "@webassemblyjs/ieee754": "1.8.5",
+        "@webassemblyjs/leb128": "1.8.5",
+        "@webassemblyjs/utf8": "1.8.5"
+      }
+    },
+    "@webassemblyjs/wast-parser": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz",
+      "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/floating-point-hex-parser": "1.8.5",
+        "@webassemblyjs/helper-api-error": "1.8.5",
+        "@webassemblyjs/helper-code-frame": "1.8.5",
+        "@webassemblyjs/helper-fsm": "1.8.5",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/wast-printer": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz",
+      "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/wast-parser": "1.8.5",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@xtuc/ieee754": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+      "dev": true
+    },
+    "@xtuc/long": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+      "dev": true
+    },
+    "@yarnpkg/lockfile": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+      "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+      "dev": true
+    },
+    "JSONStream": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+      "dev": true,
+      "requires": {
+        "jsonparse": "^1.2.0",
+        "through": ">=2.2.7 <3"
+      }
+    },
+    "accepts": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+      "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+      "dev": true,
+      "requires": {
+        "mime-types": "~2.1.18",
+        "negotiator": "0.6.1"
+      }
+    },
+    "acorn": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
+      "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+      "dev": true
+    },
+    "adm-zip": {
+      "version": "0.4.13",
+      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz",
+      "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==",
+      "dev": true
+    },
+    "after": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+      "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
+      "dev": true
+    },
+    "agent-base": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+      "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+      "dev": true,
+      "requires": {
+        "es6-promisify": "^5.0.0"
+      }
+    },
+    "agentkeepalive": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
+      "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
+      "dev": true,
+      "requires": {
+        "humanize-ms": "^1.2.1"
+      }
+    },
+    "aggregate-error": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz",
+      "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=",
+      "requires": {
+        "clean-stack": "^1.0.0",
+        "indent-string": "^3.0.0"
+      },
+      "dependencies": {
+        "indent-string": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+          "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok="
+        }
+      }
+    },
+    "ajv": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz",
+      "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==",
+      "requires": {
+        "fast-deep-equal": "^2.0.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ajv-errors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+      "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+      "dev": true
+    },
+    "ajv-keywords": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
+      "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+      "dev": true
+    },
+    "amdefine": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+      "dev": true
+    },
+    "angular-bootstrap-md": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmjs.org/angular-bootstrap-md/-/angular-bootstrap-md-7.5.4.tgz",
+      "integrity": "sha512-LBVqSswd49hJ3YNTkXzwM0Cn1+9SQ2kVYQxX36ZjxaKNeDQKaUk3V2Rb/y8krhDJrPJh37pDzRmlCDObg37EIA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "angular6-json-schema-form": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/angular6-json-schema-form/-/angular6-json-schema-form-7.3.0.tgz",
+      "integrity": "sha512-ZoCCsE4KDjHF9sLcA3NnglVpA+N578R+DYMaSfc4F0UviUoil61oeGTMhVYOuFjJ6Yui0mxKY9B0sgJzvaCjxw==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "ansi-colors": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
+      "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==",
+      "dev": true
+    },
+    "ansi-escapes": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz",
+      "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.5.2"
+      }
+    },
+    "ansi-html": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
+      "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "anymatch": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+      "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+      "dev": true,
+      "requires": {
+        "micromatch": "^3.1.4",
+        "normalize-path": "^2.1.1"
+      }
+    },
+    "app-root-path": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz",
+      "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==",
+      "dev": true
+    },
+    "append-transform": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
+      "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==",
+      "dev": true,
+      "requires": {
+        "default-require-extensions": "^2.0.0"
+      }
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+      "dev": true
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "aria-query": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
+      "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
+      "dev": true,
+      "requires": {
+        "ast-types-flow": "0.0.7",
+        "commander": "^2.11.0"
+      }
+    },
+    "arr-diff": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+      "dev": true
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+      "dev": true
+    },
+    "arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+      "dev": true
+    },
+    "array-flatten": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
+      "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",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "dev": true,
+      "requires": {
+        "array-uniq": "^1.0.1"
+      }
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "dev": true
+    },
+    "array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+      "dev": true
+    },
+    "arraybuffer.slice": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
+      "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
+      "dev": true
+    },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
+    },
+    "asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+      "dev": true
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "asn1.js": {
+      "version": "4.10.1",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+      "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+      "dev": true,
+      "requires": {
+        "object-assign": "^4.1.1",
+        "util": "0.10.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+          "dev": true
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "dev": true,
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        }
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
+    "assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+      "dev": true
+    },
+    "ast-types-flow": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+      "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
+      "dev": true
+    },
+    "async": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.14"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        }
+      }
+    },
+    "async-each": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+      "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
+      "dev": true
+    },
+    "async-limiter": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true
+    },
+    "autoprefixer": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz",
+      "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==",
+      "dev": true,
+      "requires": {
+        "browserslist": "^4.6.3",
+        "caniuse-lite": "^1.0.30000980",
+        "chalk": "^2.4.2",
+        "normalize-range": "^0.1.2",
+        "num2fraction": "^1.2.2",
+        "postcss": "^7.0.17",
+        "postcss-value-parser": "^4.0.0"
+      }
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+    },
+    "aws4": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+      "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
+    },
+    "axobject-query": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
+      "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
+      "dev": true,
+      "requires": {
+        "ast-types-flow": "0.0.7"
+      }
+    },
+    "babel-code-frame": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+      "dev": true,
+      "requires": {
+        "chalk": "^1.1.3",
+        "esutils": "^2.0.2",
+        "js-tokens": "^3.0.2"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "babel-generator": {
+      "version": "6.26.1",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
+      "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
+      "dev": true,
+      "requires": {
+        "babel-messages": "^6.23.0",
+        "babel-runtime": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "detect-indent": "^4.0.0",
+        "jsesc": "^1.3.0",
+        "lodash": "^4.17.4",
+        "source-map": "^0.5.7",
+        "trim-right": "^1.0.1"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+          "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "babel-messages": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+      "dev": true,
+      "requires": {
+        "babel-runtime": "^6.22.0"
+      }
+    },
+    "babel-plugin-dynamic-import-node": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz",
+      "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==",
+      "dev": true,
+      "requires": {
+        "object.assign": "^4.1.0"
+      }
+    },
+    "babel-runtime": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+      "dev": true,
+      "requires": {
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+          "dev": true
+        }
+      }
+    },
+    "babel-template": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+      "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+      "dev": true,
+      "requires": {
+        "babel-runtime": "^6.26.0",
+        "babel-traverse": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "babylon": "^6.18.0",
+        "lodash": "^4.17.4"
+      }
+    },
+    "babel-traverse": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+      "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "^6.26.0",
+        "babel-messages": "^6.23.0",
+        "babel-runtime": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "babylon": "^6.18.0",
+        "debug": "^2.6.8",
+        "globals": "^9.18.0",
+        "invariant": "^2.2.2",
+        "lodash": "^4.17.4"
+      },
+      "dependencies": {
+        "globals": {
+          "version": "9.18.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+          "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+          "dev": true
+        }
+      }
+    },
+    "babel-types": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+      "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+      "dev": true,
+      "requires": {
+        "babel-runtime": "^6.26.0",
+        "esutils": "^2.0.2",
+        "lodash": "^4.17.4",
+        "to-fast-properties": "^1.0.3"
+      },
+      "dependencies": {
+        "to-fast-properties": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+          "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+          "dev": true
+        }
+      }
+    },
+    "babylon": {
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+      "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+      "dev": true
+    },
+    "backo2": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+      "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+      "dev": true,
+      "requires": {
+        "cache-base": "^1.0.1",
+        "class-utils": "^0.3.5",
+        "component-emitter": "^1.2.1",
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.1",
+        "mixin-deep": "^1.2.0",
+        "pascalcase": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "base64-arraybuffer": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+      "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
+      "dev": true
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+      "dev": true
+    },
+    "base64id": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
+      "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
+      "dev": true
+    },
+    "base64url": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
+      "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
+    },
+    "batch": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+      "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+      "dev": true
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "better-assert": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+      "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+      "dev": true,
+      "requires": {
+        "callsite": "1.0.0"
+      }
+    },
+    "big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+      "dev": true
+    },
+    "binary-extensions": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
+      "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==",
+      "dev": true
+    },
+    "blob": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
+      "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
+      "dev": true
+    },
+    "blocking-proxy": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz",
+      "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "bluebird": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
+      "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==",
+      "dev": true
+    },
+    "bn.js": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+      "dev": true
+    },
+    "body-parser": {
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+      "dev": true,
+      "requires": {
+        "bytes": "3.0.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "~1.6.3",
+        "iconv-lite": "0.4.23",
+        "on-finished": "~2.3.0",
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "~1.6.16"
+      }
+    },
+    "bonjour": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
+      "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+      "dev": true,
+      "requires": {
+        "array-flatten": "^2.1.0",
+        "deep-equal": "^1.0.1",
+        "dns-equal": "^1.0.0",
+        "dns-txt": "^2.0.2",
+        "multicast-dns": "^6.0.1",
+        "multicast-dns-service-types": "^1.1.0"
+      }
+    },
+    "bootstrap": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
+      "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+      "dev": true,
+      "requires": {
+        "arr-flatten": "^1.1.0",
+        "array-unique": "^0.3.2",
+        "extend-shallow": "^2.0.1",
+        "fill-range": "^4.0.0",
+        "isobject": "^3.0.1",
+        "repeat-element": "^1.1.2",
+        "snapdragon": "^0.8.1",
+        "snapdragon-node": "^2.0.1",
+        "split-string": "^3.0.2",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+      "dev": true
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "dev": true,
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "browserify-cipher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "dev": true,
+      "requires": {
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
+      }
+    },
+    "browserify-des": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "browserify-rsa": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "randombytes": "^2.0.1"
+      }
+    },
+    "browserify-sign": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+      "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.1",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.2",
+        "elliptic": "^6.0.0",
+        "inherits": "^2.0.1",
+        "parse-asn1": "^5.0.0"
+      }
+    },
+    "browserify-zlib": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "dev": true,
+      "requires": {
+        "pako": "~1.0.5"
+      }
+    },
+    "browserslist": {
+      "version": "4.6.6",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz",
+      "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==",
+      "dev": true,
+      "requires": {
+        "caniuse-lite": "^1.0.30000984",
+        "electron-to-chromium": "^1.3.191",
+        "node-releases": "^1.1.25"
+      }
+    },
+    "browserstack": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.2.tgz",
+      "integrity": "sha512-+6AFt9HzhKykcPF79W6yjEUJcdvZOV0lIXdkORXMJftGrDl0OKWqRF4GHqpDNkxiceDT/uB7Fb/aDwktvXX7dg==",
+      "dev": true,
+      "requires": {
+        "https-proxy-agent": "^2.2.1"
+      }
+    },
+    "buffer": {
+      "version": "4.9.1",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+      "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+      "dev": true,
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4",
+        "isarray": "^1.0.0"
+      }
+    },
+    "buffer-alloc": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+      "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+      "dev": true,
+      "requires": {
+        "buffer-alloc-unsafe": "^1.1.0",
+        "buffer-fill": "^1.0.0"
+      }
+    },
+    "buffer-alloc-unsafe": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+      "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+      "dev": true
+    },
+    "buffer-fill": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+      "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+      "dev": true
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
+    "buffer-indexof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
+      "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
+      "dev": true
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+      "dev": true
+    },
+    "builtin-modules": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+      "dev": true
+    },
+    "builtin-status-codes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+      "dev": true
+    },
+    "builtins": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+      "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
+      "dev": true
+    },
+    "byline": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
+      "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
+    },
+    "bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+      "dev": true
+    },
+    "cacache": {
+      "version": "12.0.2",
+      "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz",
+      "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.5",
+        "chownr": "^1.1.1",
+        "figgy-pudding": "^3.5.1",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.1.15",
+        "infer-owner": "^1.0.3",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "move-concurrently": "^1.0.1",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^2.6.3",
+        "ssri": "^6.0.1",
+        "unique-filename": "^1.1.1",
+        "y18n": "^4.0.0"
+      },
+      "dependencies": {
+        "bluebird": {
+          "version": "3.7.0",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.0.tgz",
+          "integrity": "sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg==",
+          "dev": true
+        },
+        "glob": {
+          "version": "7.1.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+          "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "cache-base": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+      "dev": true,
+      "requires": {
+        "collection-visit": "^1.0.0",
+        "component-emitter": "^1.2.1",
+        "get-value": "^2.0.6",
+        "has-value": "^1.0.0",
+        "isobject": "^3.0.1",
+        "set-value": "^2.0.0",
+        "to-object-path": "^0.3.0",
+        "union-value": "^1.0.0",
+        "unset-value": "^1.0.0"
+      }
+    },
+    "cacheable-request": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz",
+      "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=",
+      "requires": {
+        "clone-response": "1.0.2",
+        "get-stream": "3.0.0",
+        "http-cache-semantics": "3.8.1",
+        "keyv": "3.0.0",
+        "lowercase-keys": "1.0.0",
+        "normalize-url": "2.0.1",
+        "responselike": "1.0.2"
+      },
+      "dependencies": {
+        "lowercase-keys": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+          "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY="
+        }
+      }
+    },
+    "caller-callsite": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+      "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+      "dev": true,
+      "requires": {
+        "callsites": "^2.0.0"
+      }
+    },
+    "caller-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+      "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+      "dev": true,
+      "requires": {
+        "caller-callsite": "^2.0.0"
+      }
+    },
+    "callsite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+      "dev": true
+    },
+    "callsites": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+      "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+      "dev": true
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true
+    },
+    "caniuse-lite": {
+      "version": "1.0.30000989",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz",
+      "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==",
+      "dev": true
+    },
+    "canonical-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz",
+      "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "dependencies": {
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "chart.js": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz",
+      "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==",
+      "requires": {
+        "chartjs-color": "^2.1.0",
+        "moment": "^2.10.2"
+      }
+    },
+    "chartjs-color": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
+      "integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
+      "requires": {
+        "chartjs-color-string": "^0.5.0",
+        "color-convert": "^0.5.3"
+      },
+      "dependencies": {
+        "color-convert": {
+          "version": "0.5.3",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
+          "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
+        }
+      }
+    },
+    "chartjs-color-string": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
+      "integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
+      "requires": {
+        "color-name": "^1.0.0"
+      }
+    },
+    "chokidar": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
+      "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
+      "dev": true,
+      "requires": {
+        "anymatch": "^2.0.0",
+        "async-each": "^1.0.0",
+        "braces": "^2.3.0",
+        "fsevents": "^1.2.2",
+        "glob-parent": "^3.1.0",
+        "inherits": "^2.0.1",
+        "is-binary-path": "^1.0.0",
+        "is-glob": "^4.0.0",
+        "lodash.debounce": "^4.0.8",
+        "normalize-path": "^2.1.1",
+        "path-is-absolute": "^1.0.0",
+        "readdirp": "^2.0.0",
+        "upath": "^1.0.5"
+      }
+    },
+    "chownr": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
+      "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
+      "dev": true
+    },
+    "chrome-trace-event": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+      "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "circular-dependency-plugin": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz",
+      "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw==",
+      "dev": true
+    },
+    "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
+    },
+    "class-utils": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "define-property": "^0.2.5",
+        "isobject": "^3.0.0",
+        "static-extend": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "clean-css": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
+      "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
+      "dev": true,
+      "requires": {
+        "source-map": "~0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "clean-stack": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz",
+      "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE="
+    },
+    "cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "cli-width": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+      "dev": true
+    },
+    "cliui": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+      "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+      "dev": true,
+      "requires": {
+        "string-width": "^2.1.1",
+        "strip-ansi": "^4.0.0",
+        "wrap-ansi": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+      "dev": true
+    },
+    "clone-deep": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+      "dev": true,
+      "requires": {
+        "is-plain-object": "^2.0.4",
+        "kind-of": "^6.0.2",
+        "shallow-clone": "^3.0.0"
+      }
+    },
+    "clone-response": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
+      "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
+      "requires": {
+        "mimic-response": "^1.0.0"
+      }
+    },
+    "co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+      "dev": true
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+      "dev": true
+    },
+    "codelyzer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.1.2.tgz",
+      "integrity": "sha512-1z7mtpwxcz5uUqq0HLO0ifj/tz2dWEmeaK+8c5TEZXAwwVxrjjg0118ODCOCCOcpfYaaEHxStNCaWVYo9FUPXw==",
+      "dev": true,
+      "requires": {
+        "app-root-path": "^2.2.1",
+        "aria-query": "^3.0.0",
+        "axobject-query": "^2.0.2",
+        "css-selector-tokenizer": "^0.7.1",
+        "cssauron": "^1.4.0",
+        "damerau-levenshtein": "^1.0.4",
+        "semver-dsl": "^1.0.1",
+        "source-map": "^0.5.7",
+        "sprintf-js": "^1.1.2"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        },
+        "sprintf-js": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+          "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+          "dev": true
+        }
+      }
+    },
+    "collection-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+      "dev": true,
+      "requires": {
+        "map-visit": "^1.0.0",
+        "object-visit": "^1.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+    },
+    "colors": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+      "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",
+      "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "commander": {
+      "version": "2.17.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+      "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
+      "dev": true
+    },
+    "commondir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+      "dev": true
+    },
+    "compare-versions": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.0.tgz",
+      "integrity": "sha512-hX+4kt2Rcwu+x1U0SsEFCn1quURjEjPEGH/cPBlpME/IidGimAdwfMU+B+xDr7et/KTR7VH2+ZqWGerv4NGs2w==",
+      "dev": true
+    },
+    "component-bind": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+      "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
+      "dev": true
+    },
+    "component-emitter": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+      "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+      "dev": true
+    },
+    "component-inherit": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+      "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
+      "dev": true
+    },
+    "compressible": {
+      "version": "2.0.17",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
+      "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==",
+      "dev": true,
+      "requires": {
+        "mime-db": ">= 1.40.0 < 2"
+      },
+      "dependencies": {
+        "mime-db": {
+          "version": "1.42.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",
+          "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==",
+          "dev": true
+        }
+      }
+    },
+    "compression": {
+      "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.16",
+        "debug": "2.6.9",
+        "on-headers": "~1.0.2",
+        "safe-buffer": "5.1.2",
+        "vary": "~1.1.2"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "connect": {
+      "version": "3.6.6",
+      "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz",
+      "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "finalhandler": "1.1.0",
+        "parseurl": "~1.3.2",
+        "utils-merge": "1.0.1"
+      },
+      "dependencies": {
+        "finalhandler": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
+          "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
+          "dev": true,
+          "requires": {
+            "debug": "2.6.9",
+            "encodeurl": "~1.0.1",
+            "escape-html": "~1.0.3",
+            "on-finished": "~2.3.0",
+            "parseurl": "~1.3.2",
+            "statuses": "~1.3.1",
+            "unpipe": "~1.0.0"
+          }
+        },
+        "statuses": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
+          "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
+          "dev": true
+        }
+      }
+    },
+    "connect-history-api-fallback": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
+      "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
+      "dev": true
+    },
+    "console-browserify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+      "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+      "dev": true,
+      "requires": {
+        "date-now": "^0.1.4"
+      }
+    },
+    "constants-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+      "dev": true
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+      "dev": true
+    },
+    "convert-source-map": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
+      "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "~5.1.1"
+      }
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+      "dev": true
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+      "dev": true
+    },
+    "copy-concurrently": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+      "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "fs-write-stream-atomic": "^1.0.8",
+        "iferr": "^0.1.5",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.0"
+      }
+    },
+    "copy-descriptor": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+      "dev": true
+    },
+    "copy-webpack-plugin": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz",
+      "integrity": "sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==",
+      "dev": true,
+      "requires": {
+        "cacache": "^11.3.3",
+        "find-cache-dir": "^2.1.0",
+        "glob-parent": "^3.1.0",
+        "globby": "^7.1.1",
+        "is-glob": "^4.0.1",
+        "loader-utils": "^1.2.3",
+        "minimatch": "^3.0.4",
+        "normalize-path": "^3.0.0",
+        "p-limit": "^2.2.0",
+        "schema-utils": "^1.0.0",
+        "serialize-javascript": "^1.7.0",
+        "webpack-log": "^2.0.0"
+      },
+      "dependencies": {
+        "bluebird": {
+          "version": "3.7.0",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.0.tgz",
+          "integrity": "sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg==",
+          "dev": true
+        },
+        "cacache": {
+          "version": "11.3.3",
+          "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz",
+          "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==",
+          "dev": true,
+          "requires": {
+            "bluebird": "^3.5.5",
+            "chownr": "^1.1.1",
+            "figgy-pudding": "^3.5.1",
+            "glob": "^7.1.4",
+            "graceful-fs": "^4.1.15",
+            "lru-cache": "^5.1.1",
+            "mississippi": "^3.0.0",
+            "mkdirp": "^0.5.1",
+            "move-concurrently": "^1.0.1",
+            "promise-inflight": "^1.0.1",
+            "rimraf": "^2.6.3",
+            "ssri": "^6.0.1",
+            "unique-filename": "^1.1.1",
+            "y18n": "^4.0.0"
+          }
+        },
+        "find-cache-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+          "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+          "dev": true,
+          "requires": {
+            "commondir": "^1.0.1",
+            "make-dir": "^2.0.0",
+            "pkg-dir": "^3.0.0"
+          }
+        },
+        "glob": {
+          "version": "7.1.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+          "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "is-glob": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+          "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+          "dev": true,
+          "requires": {
+            "is-extglob": "^2.1.1"
+          }
+        },
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "normalize-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+          "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+          "dev": true
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "core-js": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
+      "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
+    },
+    "core-js-compat": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.2.1.tgz",
+      "integrity": "sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==",
+      "dev": true,
+      "requires": {
+        "browserslist": "^4.6.6",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "cosmiconfig": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+      "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+      "dev": true,
+      "requires": {
+        "import-fresh": "^2.0.0",
+        "is-directory": "^0.3.1",
+        "js-yaml": "^3.13.1",
+        "parse-json": "^4.0.0"
+      }
+    },
+    "create-ecdh": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+      "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.0.0"
+      }
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "crypto-browserify": {
+      "version": "3.12.0",
+      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "dev": true,
+      "requires": {
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
+      }
+    },
+    "css-parse": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
+      "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=",
+      "dev": true
+    },
+    "css-selector-tokenizer": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz",
+      "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==",
+      "dev": true,
+      "requires": {
+        "cssesc": "^0.1.0",
+        "fastparse": "^1.1.1",
+        "regexpu-core": "^1.0.0"
+      }
+    },
+    "cssauron": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
+      "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
+      "dev": true,
+      "requires": {
+        "through": "X.X.X"
+      }
+    },
+    "cssesc": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+      "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
+      "dev": true
+    },
+    "custom-event": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
+      "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=",
+      "dev": true
+    },
+    "cyclist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+      "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+      "dev": true
+    },
+    "damerau-levenshtein": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz",
+      "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==",
+      "dev": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "date-format": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
+      "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=",
+      "dev": true
+    },
+    "date-now": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+      "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dev": true,
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "debuglog": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
+      "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
+      "dev": true
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "decode-uri-component": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+    },
+    "decompress-response": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
+      "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+      "requires": {
+        "mimic-response": "^1.0.0"
+      }
+    },
+    "deep-equal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz",
+      "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==",
+      "dev": true,
+      "requires": {
+        "is-arguments": "^1.0.4",
+        "is-date-object": "^1.0.1",
+        "is-regex": "^1.0.4",
+        "object-is": "^1.0.1",
+        "object-keys": "^1.1.1",
+        "regexp.prototype.flags": "^1.2.0"
+      }
+    },
+    "default-gateway": {
+      "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",
+        "ip-regex": "^2.1.0"
+      }
+    },
+    "default-require-extensions": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
+      "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=",
+      "dev": true,
+      "requires": {
+        "strip-bom": "^3.0.0"
+      },
+      "dependencies": {
+        "strip-bom": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+          "dev": true
+        }
+      }
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "define-property": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "dev": true,
+      "requires": {
+        "is-descriptor": "^1.0.2",
+        "isobject": "^3.0.1"
+      },
+      "dependencies": {
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "del": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
+      "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
+      "dev": true,
+      "requires": {
+        "@types/glob": "^7.1.1",
+        "globby": "^6.1.0",
+        "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": {
+          "version": "6.1.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+          "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+          "dev": true,
+          "requires": {
+            "array-union": "^1.0.1",
+            "glob": "^7.0.3",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          },
+          "dependencies": {
+            "pify": {
+              "version": "2.3.0",
+              "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+              "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+              "dev": true
+            }
+          }
+        },
+        "is-path-cwd": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
+          "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
+          "dev": true
+        },
+        "is-path-in-cwd": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+          "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
+          "dev": true,
+          "requires": {
+            "is-path-inside": "^2.1.0"
+          }
+        },
+        "is-path-inside": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+          "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
+          "dev": true,
+          "requires": {
+            "path-is-inside": "^1.0.2"
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        }
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+      "dev": true
+    },
+    "dependency-graph": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz",
+      "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==",
+      "dev": true
+    },
+    "des.js": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+      "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+      "dev": true
+    },
+    "detect-indent": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+      "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+      "dev": true,
+      "requires": {
+        "repeating": "^2.0.0"
+      }
+    },
+    "detect-node": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+      "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
+      "dev": true
+    },
+    "dezalgo": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+      "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
+      "dev": true,
+      "requires": {
+        "asap": "^2.0.0",
+        "wrappy": "1"
+      }
+    },
+    "di": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
+      "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
+      "dev": true
+    },
+    "diff": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "dev": true
+    },
+    "diffie-hellman": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
+      }
+    },
+    "dir-glob": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
+      "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
+      "dev": true,
+      "requires": {
+        "path-type": "^3.0.0"
+      }
+    },
+    "dns-equal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
+      "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
+      "dev": true
+    },
+    "dns-packet": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
+      "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
+      "dev": true,
+      "requires": {
+        "ip": "^1.1.0",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "dns-txt": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
+      "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+      "dev": true,
+      "requires": {
+        "buffer-indexof": "^1.0.0"
+      }
+    },
+    "dom-serialize": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
+      "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
+      "dev": true,
+      "requires": {
+        "custom-event": "~1.0.0",
+        "ent": "~2.2.0",
+        "extend": "^3.0.0",
+        "void-elements": "^2.0.0"
+      }
+    },
+    "domain-browser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+      "dev": true
+    },
+    "duplexer3": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
+    },
+    "duplexify": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+      "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+      "dev": true
+    },
+    "electron-to-chromium": {
+      "version": "1.3.279",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.279.tgz",
+      "integrity": "sha512-iiBT/LeUWKnhd7d/n4IZsx/NIacs7gjFgAT1q5/i0POiS+5d0rVnbbyCRMmsBW7vaQJOUhWyh4PsyIVZb/Ax5Q==",
+      "dev": true
+    },
+    "elliptic": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
+      "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      }
+    },
+    "emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "emojis-list": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+      "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+      "dev": true
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "dev": true
+    },
+    "encoding": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "dev": true,
+      "requires": {
+        "iconv-lite": "~0.4.13"
+      }
+    },
+    "end-of-stream": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+      "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+      "requires": {
+        "once": "^1.4.0"
+      }
+    },
+    "engine.io": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz",
+      "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.4",
+        "base64id": "1.0.0",
+        "cookie": "0.3.1",
+        "debug": "~3.1.0",
+        "engine.io-parser": "~2.1.0",
+        "ws": "~3.3.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "engine.io-client": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
+      "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
+      "dev": true,
+      "requires": {
+        "component-emitter": "1.2.1",
+        "component-inherit": "0.0.3",
+        "debug": "~3.1.0",
+        "engine.io-parser": "~2.1.1",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "ws": "~3.3.1",
+        "xmlhttprequest-ssl": "~1.5.4",
+        "yeast": "0.1.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "engine.io-parser": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
+      "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
+      "dev": true,
+      "requires": {
+        "after": "0.8.2",
+        "arraybuffer.slice": "~0.0.7",
+        "base64-arraybuffer": "0.1.5",
+        "blob": "0.0.5",
+        "has-binary2": "~1.0.2"
+      }
+    },
+    "enhanced-resolve": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
+      "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "memory-fs": "^0.4.0",
+        "tapable": "^1.0.0"
+      }
+    },
+    "ent": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+      "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
+      "dev": true
+    },
+    "err-code": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
+      "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=",
+      "dev": true
+    },
+    "errno": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "dev": true,
+      "requires": {
+        "prr": "~1.0.1"
+      }
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.15.0.tgz",
+      "integrity": "sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ==",
+      "dev": true,
+      "requires": {
+        "es-to-primitive": "^1.2.0",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.0",
+        "is-callable": "^1.1.4",
+        "is-regex": "^1.0.4",
+        "object-inspect": "^1.6.0",
+        "object-keys": "^1.1.1",
+        "string.prototype.trimleft": "^2.1.0",
+        "string.prototype.trimright": "^2.1.0"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+      "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "es6-promise": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
+      "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==",
+      "dev": true
+    },
+    "es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+      "dev": true,
+      "requires": {
+        "es6-promise": "^4.0.3"
+      }
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+      "dev": true
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "eslint-scope": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+      "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "esrecurse": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^4.1.0"
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+      "dev": true
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "dev": true
+    },
+    "eventemitter3": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
+      "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
+      "dev": true
+    },
+    "events": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
+      "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
+      "dev": true
+    },
+    "eventsource": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
+      "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
+      "dev": true,
+      "requires": {
+        "original": "^1.0.0"
+      }
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "dev": true,
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "execa": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+      "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+      "dev": true,
+      "requires": {
+        "cross-spawn": "^6.0.0",
+        "get-stream": "^4.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
+      },
+      "dependencies": {
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        }
+      }
+    },
+    "exit": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+      "dev": true
+    },
+    "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-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"
+          }
+        },
+        "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-brackets": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+      "dev": true,
+      "requires": {
+        "debug": "^2.3.3",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "posix-character-classes": "^0.1.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "accepts": {
+          "version": "1.3.7",
+          "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+          "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+          "dev": true,
+          "requires": {
+            "mime-types": "~2.1.24",
+            "negotiator": "0.6.2"
+          }
+        },
+        "array-flatten": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+          "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+          "dev": true
+        },
+        "body-parser": {
+          "version": "1.19.0",
+          "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+          "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+          "dev": true,
+          "requires": {
+            "bytes": "3.1.0",
+            "content-type": "~1.0.4",
+            "debug": "2.6.9",
+            "depd": "~1.1.2",
+            "http-errors": "1.7.2",
+            "iconv-lite": "0.4.24",
+            "on-finished": "~2.3.0",
+            "qs": "6.7.0",
+            "raw-body": "2.4.0",
+            "type-is": "~1.6.17"
+          }
+        },
+        "bytes": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+          "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+          "dev": true
+        },
+        "cookie": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+          "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+          "dev": true
+        },
+        "http-errors": {
+          "version": "1.7.2",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+          "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+          "dev": true,
+          "requires": {
+            "depd": "~1.1.2",
+            "inherits": "2.0.3",
+            "setprototypeof": "1.1.1",
+            "statuses": ">= 1.5.0 < 2",
+            "toidentifier": "1.0.0"
+          }
+        },
+        "iconv-lite": {
+          "version": "0.4.24",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+          "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+          "dev": true,
+          "requires": {
+            "safer-buffer": ">= 2.1.2 < 3"
+          }
+        },
+        "mime-db": {
+          "version": "1.40.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+          "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+          "dev": true
+        },
+        "mime-types": {
+          "version": "2.1.24",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+          "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+          "dev": true,
+          "requires": {
+            "mime-db": "1.40.0"
+          }
+        },
+        "negotiator": {
+          "version": "0.6.2",
+          "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+          "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+          "dev": true
+        },
+        "parseurl": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+          "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+          "dev": true
+        },
+        "qs": {
+          "version": "6.7.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+          "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+          "dev": true
+        },
+        "range-parser": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+          "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+          "dev": true
+        },
+        "raw-body": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+          "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+          "dev": true,
+          "requires": {
+            "bytes": "3.1.0",
+            "http-errors": "1.7.2",
+            "iconv-lite": "0.4.24",
+            "unpipe": "1.0.0"
+          }
+        },
+        "setprototypeof": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+          "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+          "dev": true
+        },
+        "statuses": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+          "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+          "dev": true
+        },
+        "type-is": {
+          "version": "1.6.18",
+          "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+          "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+          "dev": true,
+          "requires": {
+            "media-typer": "0.3.0",
+            "mime-types": "~2.1.24"
+          }
+        }
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "dev": true,
+      "requires": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      },
+      "dependencies": {
+        "iconv-lite": {
+          "version": "0.4.24",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+          "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+          "dev": true,
+          "requires": {
+            "safer-buffer": ">= 2.1.2 < 3"
+          }
+        }
+      }
+    },
+    "extglob": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+      "dev": true,
+      "requires": {
+        "array-unique": "^0.3.2",
+        "define-property": "^1.0.0",
+        "expand-brackets": "^2.1.4",
+        "extend-shallow": "^2.0.1",
+        "fragment-cache": "^0.2.1",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+    },
+    "fast-deep-equal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+    },
+    "fastparse": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+      "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
+      "dev": true
+    },
+    "faye-websocket": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+      "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+      "dev": true,
+      "requires": {
+        "websocket-driver": ">=0.5.1"
+      }
+    },
+    "figgy-pudding": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+      "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+      "dev": true
+    },
+    "figures": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz",
+      "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "file-loader": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.2.0.tgz",
+      "integrity": "sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.2.3",
+        "schema-utils": "^2.0.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "schema-utils": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.4.1.tgz",
+          "integrity": "sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==",
+          "dev": true,
+          "requires": {
+            "ajv": "^6.10.2",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "fileset": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
+      "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=",
+      "dev": true,
+      "requires": {
+        "glob": "^7.0.3",
+        "minimatch": "^3.0.3"
+      }
+    },
+    "fill-range": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1",
+        "to-regex-range": "^2.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "dependencies": {
+        "parseurl": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+          "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+          "dev": true
+        },
+        "statuses": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+          "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+          "dev": true
+        }
+      }
+    },
+    "find-cache-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz",
+      "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==",
+      "dev": true,
+      "requires": {
+        "commondir": "^1.0.1",
+        "make-dir": "^3.0.0",
+        "pkg-dir": "^4.1.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "make-dir": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz",
+          "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==",
+          "dev": true,
+          "requires": {
+            "semver": "^6.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+          "dev": true
+        },
+        "pkg-dir": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+          "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+          "dev": true,
+          "requires": {
+            "find-up": "^4.0.0"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "find-up": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+      "dev": true,
+      "requires": {
+        "locate-path": "^3.0.0"
+      }
+    },
+    "flatted": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
+      "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==",
+      "dev": true
+    },
+    "flush-write-stream": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+      "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.3.6"
+      }
+    },
+    "follow-redirects": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.6.1.tgz",
+      "integrity": "sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==",
+      "dev": true,
+      "requires": {
+        "debug": "=3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+      "dev": true
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+      "dev": true
+    },
+    "fragment-cache": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+      "dev": true,
+      "requires": {
+        "map-cache": "^0.2.2"
+      }
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "dev": true
+    },
+    "from2": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
+      }
+    },
+    "fs-access": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
+      "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=",
+      "dev": true,
+      "requires": {
+        "null-check": "^1.0.0"
+      }
+    },
+    "fs-minipass": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
+      "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
+      "dev": true,
+      "requires": {
+        "minipass": "^2.6.0"
+      }
+    },
+    "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",
+      "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "iferr": "^0.1.5",
+        "imurmurhash": "^0.1.4",
+        "readable-stream": "1 || 2"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "fsevents": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz",
+      "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "nan": "^2.9.2",
+        "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,
+          "optional": 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,
+          "optional": true
+        },
+        "brace-expansion": {
+          "version": "1.1.11",
+          "bundled": true,
+          "dev": true,
+          "optional": 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,
+          "optional": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "console-control-strings": {
+          "version": "1.1.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "debug": {
+          "version": "2.6.9",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "deep-extend": {
+          "version": "0.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,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "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,
+          "optional": true
+        },
+        "ini": {
+          "version": "1.3.5",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "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,
+          "optional": true,
+          "requires": {
+            "brace-expansion": "^1.1.7"
+          }
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "minipass": {
+          "version": "2.3.5",
+          "bundled": true,
+          "dev": true,
+          "optional": 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,
+          "optional": true,
+          "requires": {
+            "minimist": "0.0.8"
+          }
+        },
+        "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"
+          }
+        },
+        "nopt": {
+          "version": "4.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "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,
+          "optional": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "bundled": true,
+          "dev": true,
+          "optional": 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
+            }
+          }
+        },
+        "readable-stream": {
+          "version": "2.3.6",
+          "bundled": true,
+          "dev": true,
+          "optional": 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"
+          }
+        },
+        "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,
+          "optional": 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,
+          "optional": 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,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "bundled": true,
+          "dev": true,
+          "optional": true,
+          "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,
+          "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"
+          }
+        },
+        "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,
+          "optional": true
+        },
+        "yallist": {
+          "version": "3.0.3",
+          "bundled": true,
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "genfun": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz",
+      "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==",
+      "dev": true
+    },
+    "get-caller-file": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+      "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+      "dev": true
+    },
+    "get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+    },
+    "get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+      "dev": true
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "glob": {
+      "version": "7.1.3",
+      "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",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+      "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+      "dev": true,
+      "requires": {
+        "is-glob": "^3.1.0",
+        "path-dirname": "^1.0.0"
+      },
+      "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"
+          }
+        }
+      }
+    },
+    "globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true
+    },
+    "globby": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
+      "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
+      "dev": true,
+      "requires": {
+        "array-union": "^1.0.1",
+        "dir-glob": "^2.0.0",
+        "glob": "^7.1.2",
+        "ignore": "^3.3.5",
+        "pify": "^3.0.0",
+        "slash": "^1.0.0"
+      }
+    },
+    "got": {
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz",
+      "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==",
+      "requires": {
+        "@sindresorhus/is": "^0.7.0",
+        "cacheable-request": "^2.1.1",
+        "decompress-response": "^3.3.0",
+        "duplexer3": "^0.1.4",
+        "get-stream": "^3.0.0",
+        "into-stream": "^3.1.0",
+        "is-retry-allowed": "^1.1.0",
+        "isurl": "^1.0.0-alpha5",
+        "lowercase-keys": "^1.0.0",
+        "mimic-response": "^1.0.0",
+        "p-cancelable": "^0.4.0",
+        "p-timeout": "^2.0.1",
+        "pify": "^3.0.0",
+        "safe-buffer": "^5.1.1",
+        "timed-out": "^4.0.1",
+        "url-parse-lax": "^3.0.0",
+        "url-to-options": "^1.0.1"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.1.15",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+      "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
+      "dev": true
+    },
+    "hammerjs": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
+      "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
+    },
+    "handle-thing": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz",
+      "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==",
+      "dev": true
+    },
+    "handlebars": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
+      "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
+      "dev": true,
+      "requires": {
+        "neo-async": "^2.6.0",
+        "optimist": "^0.6.1",
+        "source-map": "^0.6.1",
+        "uglify-js": "^3.1.4"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "has-binary2": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
+      "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
+      "dev": true,
+      "requires": {
+        "isarray": "2.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+          "dev": true
+        }
+      }
+    },
+    "has-cors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+      "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
+      "dev": true
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "has-symbol-support-x": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz",
+      "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw=="
+    },
+    "has-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+      "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+      "dev": true
+    },
+    "has-to-string-tag-x": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz",
+      "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==",
+      "requires": {
+        "has-symbol-support-x": "^1.4.1"
+      }
+    },
+    "has-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+      "dev": true,
+      "requires": {
+        "get-value": "^2.0.6",
+        "has-values": "^1.0.0",
+        "isobject": "^3.0.0"
+      }
+    },
+    "has-values": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "kind-of": "^4.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "hash-base": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+      "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "dev": true,
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "hosted-git-info": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
+      "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
+      "dev": true
+    },
+    "hpack.js": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+      "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "obuf": "^1.0.0",
+        "readable-stream": "^2.0.1",
+        "wbuf": "^1.1.0"
+      }
+    },
+    "html-entities": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
+      "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
+      "dev": true
+    },
+    "http-cache-semantics": {
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+      "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w=="
+    },
+    "http-deceiver": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+      "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
+      "dev": true
+    },
+    "http-errors": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+      "dev": true,
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.0",
+        "statuses": ">= 1.4.0 < 2"
+      }
+    },
+    "http-parser-js": {
+      "version": "0.4.10",
+      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
+      "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
+      "dev": true
+    },
+    "http-proxy": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
+      "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
+      "dev": true,
+      "requires": {
+        "eventemitter3": "^3.0.0",
+        "follow-redirects": "^1.0.0",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "http-proxy-agent": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+      "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "4",
+        "debug": "3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "http-proxy-middleware": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
+      "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
+      "dev": true,
+      "requires": {
+        "http-proxy": "^1.17.0",
+        "is-glob": "^4.0.0",
+        "lodash": "^4.17.11",
+        "micromatch": "^3.1.10"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "https-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+      "dev": true
+    },
+    "https-proxy-agent": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
+      "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
+      "dev": true,
+      "requires": {
+        "agent-base": "^4.1.0",
+        "debug": "^3.1.0"
+      },
+      "dependencies": {
+        "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"
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        }
+      }
+    },
+    "humanize-ms": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+      "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+      "dev": true,
+      "requires": {
+        "ms": "^2.0.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+      "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+      "dev": true
+    },
+    "iferr": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+      "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+      "dev": true
+    },
+    "ignore": {
+      "version": "3.3.10",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+      "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
+      "dev": true
+    },
+    "ignore-walk": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
+      "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
+      "dev": 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",
+      "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
+      "dev": true,
+      "optional": true
+    },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
+      "dev": true
+    },
+    "import-cwd": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
+      "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=",
+      "dev": true,
+      "requires": {
+        "import-from": "^2.1.0"
+      }
+    },
+    "import-fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+      "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+      "dev": true,
+      "requires": {
+        "caller-path": "^2.0.0",
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "import-from": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
+      "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=",
+      "dev": true,
+      "requires": {
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "import-local": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+      "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+      "dev": true,
+      "requires": {
+        "pkg-dir": "^3.0.0",
+        "resolve-cwd": "^2.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "indexof": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+      "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+      "dev": true
+    },
+    "infer-owner": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+      "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
+    },
+    "inquirer": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz",
+      "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^2.4.2",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.15",
+        "mute-stream": "0.0.8",
+        "run-async": "^2.2.0",
+        "rxjs": "^6.4.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^5.1.0",
+        "through": "^2.3.6"
+      },
+      "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
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz",
+          "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^5.2.0"
+          }
+        },
+        "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"
+          }
+        }
+      }
+    },
+    "internal-ip": {
+      "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.2.0",
+        "ipaddr.js": "^1.9.0"
+      }
+    },
+    "interpret": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
+      "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
+    },
+    "into-stream": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
+      "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=",
+      "requires": {
+        "from2": "^2.1.1",
+        "p-is-promise": "^1.1.0"
+      },
+      "dependencies": {
+        "p-is-promise": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
+          "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4="
+        }
+      }
+    },
+    "invariant": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+      "dev": true,
+      "requires": {
+        "loose-envify": "^1.0.0"
+      }
+    },
+    "invert-kv": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+      "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+      "dev": true
+    },
+    "ip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+      "dev": true
+    },
+    "ip-regex": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+      "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
+      "dev": true
+    },
+    "ipaddr.js": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+      "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==",
+      "dev": true
+    },
+    "is-absolute-url": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz",
+      "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==",
+      "dev": true
+    },
+    "is-accessor-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-arguments": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
+      "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-binary-path": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^1.0.0"
+      }
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+      "dev": true
+    },
+    "is-callable": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+      "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+      "dev": true
+    },
+    "is-data-descriptor": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+      "dev": true
+    },
+    "is-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+      "dev": true,
+      "requires": {
+        "is-accessor-descriptor": "^0.1.6",
+        "is-data-descriptor": "^0.1.4",
+        "kind-of": "^5.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+          "dev": true
+        }
+      }
+    },
+    "is-directory": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+      "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+      "dev": true
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-finite": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+      "dev": true,
+      "requires": {
+        "number-is-nan": "^1.0.0"
+      }
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+      "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-object": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
+      "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA="
+    },
+    "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=",
+      "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==",
+      "dev": true,
+      "requires": {
+        "is-path-inside": "^1.0.0"
+      }
+    },
+    "is-path-inside": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+      "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+      "dev": true,
+      "requires": {
+        "path-is-inside": "^1.0.1"
+      }
+    },
+    "is-plain-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+      "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+      "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.1"
+      }
+    },
+    "is-retry-allowed": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+      "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+    },
+    "is-symbol": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+      "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.0"
+      }
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+      "dev": true
+    },
+    "is-wsl": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+      "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "isbinaryfile": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz",
+      "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
+      "dev": true,
+      "requires": {
+        "buffer-alloc": "^1.2.0"
+      }
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+    },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
+    "isomorphic-ws": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
+      "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "istanbul-api": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.6.tgz",
+      "integrity": "sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==",
+      "dev": true,
+      "requires": {
+        "async": "^2.6.2",
+        "compare-versions": "^3.4.0",
+        "fileset": "^2.0.3",
+        "istanbul-lib-coverage": "^2.0.5",
+        "istanbul-lib-hook": "^2.0.7",
+        "istanbul-lib-instrument": "^3.3.0",
+        "istanbul-lib-report": "^2.0.8",
+        "istanbul-lib-source-maps": "^3.0.6",
+        "istanbul-reports": "^2.2.4",
+        "js-yaml": "^3.13.1",
+        "make-dir": "^2.1.0",
+        "minimatch": "^3.0.4",
+        "once": "^1.4.0"
+      },
+      "dependencies": {
+        "async": {
+          "version": "2.6.2",
+          "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
+          "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
+          "dev": true,
+          "requires": {
+            "lodash": "^4.17.11"
+          }
+        },
+        "esprima": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+          "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+          "dev": true
+        },
+        "istanbul-lib-coverage": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+          "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+          "dev": true
+        },
+        "istanbul-lib-instrument": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
+          "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
+          "dev": true,
+          "requires": {
+            "@babel/generator": "^7.4.0",
+            "@babel/parser": "^7.4.3",
+            "@babel/template": "^7.4.0",
+            "@babel/traverse": "^7.4.3",
+            "@babel/types": "^7.4.0",
+            "istanbul-lib-coverage": "^2.0.5",
+            "semver": "^6.0.0"
+          }
+        },
+        "js-yaml": {
+          "version": "3.13.1",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+          "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+          "dev": true,
+          "requires": {
+            "argparse": "^1.0.7",
+            "esprima": "^4.0.0"
+          }
+        },
+        "make-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+          "dev": true,
+          "requires": {
+            "pify": "^4.0.1",
+            "semver": "^5.6.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
+            }
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "semver": {
+          "version": "6.1.2",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz",
+          "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==",
+          "dev": true
+        }
+      }
+    },
+    "istanbul-instrumenter-loader": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz",
+      "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==",
+      "dev": true,
+      "requires": {
+        "convert-source-map": "^1.5.0",
+        "istanbul-lib-instrument": "^1.7.3",
+        "loader-utils": "^1.1.0",
+        "schema-utils": "^0.3.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "5.5.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+          "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+          "dev": true,
+          "requires": {
+            "co": "^4.6.0",
+            "fast-deep-equal": "^1.0.0",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.3.0"
+          }
+        },
+        "fast-deep-equal": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
+          "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
+          "dev": true
+        },
+        "json-schema-traverse": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
+          "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
+          "dev": true
+        },
+        "schema-utils": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz",
+          "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=",
+          "dev": true,
+          "requires": {
+            "ajv": "^5.0.0"
+          }
+        }
+      }
+    },
+    "istanbul-lib-coverage": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz",
+      "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==",
+      "dev": true
+    },
+    "istanbul-lib-hook": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz",
+      "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==",
+      "dev": true,
+      "requires": {
+        "append-transform": "^1.0.0"
+      }
+    },
+    "istanbul-lib-instrument": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz",
+      "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==",
+      "dev": true,
+      "requires": {
+        "babel-generator": "^6.18.0",
+        "babel-template": "^6.16.0",
+        "babel-traverse": "^6.18.0",
+        "babel-types": "^6.18.0",
+        "babylon": "^6.18.0",
+        "istanbul-lib-coverage": "^1.2.1",
+        "semver": "^5.3.0"
+      }
+    },
+    "istanbul-lib-report": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
+      "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
+      "dev": true,
+      "requires": {
+        "istanbul-lib-coverage": "^2.0.5",
+        "make-dir": "^2.1.0",
+        "supports-color": "^6.1.0"
+      },
+      "dependencies": {
+        "istanbul-lib-coverage": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+          "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+          "dev": true
+        },
+        "make-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+          "dev": true,
+          "requires": {
+            "pify": "^4.0.1",
+            "semver": "^5.6.0"
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
+          "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
+          "dev": true
+        }
+      }
+    },
+    "istanbul-lib-source-maps": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
+      "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "istanbul-lib-coverage": "^2.0.5",
+        "make-dir": "^2.1.0",
+        "rimraf": "^2.6.3",
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "istanbul-lib-coverage": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+          "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+          "dev": true
+        },
+        "make-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+          "dev": true,
+          "requires": {
+            "pify": "^4.0.1",
+            "semver": "^5.6.0"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "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
+        },
+        "semver": {
+          "version": "5.7.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
+          "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "istanbul-reports": {
+      "version": "2.2.6",
+      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz",
+      "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==",
+      "dev": true,
+      "requires": {
+        "handlebars": "^4.1.2"
+      }
+    },
+    "isurl": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
+      "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==",
+      "requires": {
+        "has-to-string-tag-x": "^1.2.0",
+        "is-object": "^1.0.1"
+      }
+    },
+    "jasmine": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
+      "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=",
+      "dev": true,
+      "requires": {
+        "exit": "^0.1.2",
+        "glob": "^7.0.6",
+        "jasmine-core": "~2.8.0"
+      },
+      "dependencies": {
+        "jasmine-core": {
+          "version": "2.8.0",
+          "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz",
+          "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=",
+          "dev": true
+        }
+      }
+    },
+    "jasmine-core": {
+      "version": "2.99.1",
+      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
+      "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
+      "dev": true
+    },
+    "jasmine-spec-reporter": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
+      "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==",
+      "dev": true,
+      "requires": {
+        "colors": "1.1.2"
+      }
+    },
+    "jasminewd2": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz",
+      "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=",
+      "dev": true
+    },
+    "jest-worker": {
+      "version": "24.9.0",
+      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
+      "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
+      "dev": true,
+      "requires": {
+        "merge-stream": "^2.0.0",
+        "supports-color": "^6.1.0"
+      }
+    },
+    "js-levenshtein": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+      "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+      "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      },
+      "dependencies": {
+        "esprima": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+          "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+        }
+      }
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "dev": true
+    },
+    "json-buffer": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
+      "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg="
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "json3": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
+      "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==",
+      "dev": true
+    },
+    "json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "jsonparse": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+      "dev": true
+    },
+    "jsonpath-plus": {
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz",
+      "integrity": "sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg=="
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "jszip": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz",
+      "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==",
+      "dev": true,
+      "requires": {
+        "core-js": "~2.3.0",
+        "es6-promise": "~3.0.2",
+        "lie": "~3.1.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.0.6"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
+          "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=",
+          "dev": true
+        },
+        "es6-promise": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
+          "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=",
+          "dev": true
+        },
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+          "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.1",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~1.0.6",
+            "string_decoder": "~0.10.x",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        }
+      }
+    },
+    "karma": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/karma/-/karma-3.1.4.tgz",
+      "integrity": "sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.3.0",
+        "body-parser": "^1.16.1",
+        "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",
+        "mime": "^2.3.1",
+        "minimatch": "^3.0.2",
+        "optimist": "^0.6.1",
+        "qjobs": "^1.1.4",
+        "range-parser": "^1.2.0",
+        "rimraf": "^2.6.0",
+        "safe-buffer": "^5.0.1",
+        "socket.io": "2.1.1",
+        "source-map": "^0.6.1",
+        "tmp": "0.0.33",
+        "useragent": "2.3.0"
+      },
+      "dependencies": {
+        "mime": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz",
+          "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "karma-chrome-launcher": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz",
+      "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==",
+      "dev": true,
+      "requires": {
+        "fs-access": "^1.0.0",
+        "which": "^1.2.1"
+      }
+    },
+    "karma-coverage-istanbul-reporter": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.5.tgz",
+      "integrity": "sha512-yPvAlKtY3y+rKKWbOo0CzBMVTvJEeMOgbMXuVv3yWvS8YtYKC98AU9vFF0mVBZ2RP1E9SgS90+PT6Kf14P3S4w==",
+      "dev": true,
+      "requires": {
+        "istanbul-api": "^2.1.1",
+        "minimatch": "^3.0.4"
+      }
+    },
+    "karma-jasmine": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz",
+      "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=",
+      "dev": true
+    },
+    "karma-jasmine-html-reporter": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz",
+      "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=",
+      "dev": true,
+      "requires": {
+        "karma-jasmine": "^1.0.2"
+      }
+    },
+    "karma-source-map-support": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz",
+      "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==",
+      "dev": true,
+      "requires": {
+        "source-map-support": "^0.5.5"
+      }
+    },
+    "keyv": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz",
+      "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==",
+      "requires": {
+        "json-buffer": "3.0.0"
+      }
+    },
+    "killable": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
+      "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==",
+      "dev": true
+    },
+    "kind-of": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+      "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+      "dev": true
+    },
+    "lcid": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+      "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+      "dev": true,
+      "requires": {
+        "invert-kv": "^2.0.0"
+      }
+    },
+    "less": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz",
+      "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==",
+      "dev": true,
+      "requires": {
+        "clone": "^2.1.2",
+        "errno": "^0.1.1",
+        "graceful-fs": "^4.1.2",
+        "image-size": "~0.5.0",
+        "mime": "^1.4.1",
+        "mkdirp": "^0.5.0",
+        "promise": "^7.1.1",
+        "request": "^2.83.0",
+        "source-map": "~0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
+    "less-loader": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz",
+      "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==",
+      "dev": true,
+      "requires": {
+        "clone": "^2.1.1",
+        "loader-utils": "^1.1.0",
+        "pify": "^4.0.1"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        }
+      }
+    },
+    "license-webpack-plugin": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.2.tgz",
+      "integrity": "sha512-7poZHRla+ae0eEButlwMrPpkXyhNVBf2EHePYWT0jyLnI6311/OXJkTI2sOIRungRpQgU2oDMpro5bSFPT5F0A==",
+      "dev": true,
+      "requires": {
+        "@types/webpack-sources": "^0.1.5",
+        "webpack-sources": "^1.2.0"
+      }
+    },
+    "lie": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+      "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+      "dev": true,
+      "requires": {
+        "immediate": "~3.0.5"
+      }
+    },
+    "loader-runner": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+      "dev": true
+    },
+    "loader-utils": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+      "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+      "dev": true,
+      "requires": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^2.0.0",
+        "json5": "^1.0.1"
+      }
+    },
+    "locate-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+      "dev": true,
+      "requires": {
+        "p-locate": "^3.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.11",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+      "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
+    },
+    "lodash-es": {
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
+      "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
+    },
+    "lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+      "dev": true
+    },
+    "lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+      "dev": true
+    },
+    "log4js": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz",
+      "integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==",
+      "dev": true,
+      "requires": {
+        "circular-json": "^0.5.5",
+        "date-format": "^1.2.0",
+        "debug": "^3.1.0",
+        "rfdc": "^1.1.2",
+        "streamroller": "0.7.0"
+      },
+      "dependencies": {
+        "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"
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        }
+      }
+    },
+    "loglevel": {
+      "version": "1.6.4",
+      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz",
+      "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==",
+      "dev": true
+    },
+    "long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dev": true,
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "lowercase-keys": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
+    },
+    "lru-cache": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+      "dev": true,
+      "requires": {
+        "pseudomap": "^1.0.2",
+        "yallist": "^2.1.2"
+      }
+    },
+    "magic-string": {
+      "version": "0.25.3",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz",
+      "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==",
+      "dev": true,
+      "requires": {
+        "sourcemap-codec": "^1.4.4"
+      }
+    },
+    "make-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+      "dev": true,
+      "requires": {
+        "pify": "^4.0.1",
+        "semver": "^5.6.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "make-error": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
+      "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
+      "dev": true
+    },
+    "make-fetch-happen": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz",
+      "integrity": "sha512-nFr/vpL1Jc60etMVKeaLOqfGjMMb3tAHFVJWxHOFCFS04Zmd7kGlMxo0l1tzfhoQje0/UPnd0X8OeGUiXXnfPA==",
+      "dev": true,
+      "requires": {
+        "agentkeepalive": "^3.4.1",
+        "cacache": "^12.0.0",
+        "http-cache-semantics": "^3.8.1",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^2.2.1",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "node-fetch-npm": "^2.0.2",
+        "promise-retry": "^1.1.1",
+        "socks-proxy-agent": "^4.0.0",
+        "ssri": "^6.0.0"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "mamacro": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
+      "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==",
+      "dev": true
+    },
+    "map-age-cleaner": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+      "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+      "dev": true,
+      "requires": {
+        "p-defer": "^1.0.0"
+      }
+    },
+    "map-cache": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+      "dev": true
+    },
+    "map-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+      "dev": true,
+      "requires": {
+        "object-visit": "^1.0.0"
+      }
+    },
+    "material-design-icons": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz",
+      "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78="
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "dev": true
+    },
+    "mem": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
+      "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
+      "dev": true,
+      "requires": {
+        "map-age-cleaner": "^0.1.1",
+        "mimic-fn": "^2.0.0",
+        "p-is-promise": "^2.0.0"
+      }
+    },
+    "memory-fs": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+      "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+      "dev": true,
+      "requires": {
+        "errno": "^0.1.3",
+        "readable-stream": "^2.0.1"
+      }
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+      "dev": true
+    },
+    "merge-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+      "dev": true
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "braces": "^2.3.1",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "extglob": "^2.0.4",
+        "fragment-cache": "^0.2.1",
+        "kind-of": "^6.0.2",
+        "nanomatch": "^1.2.9",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.2"
+      }
+    },
+    "miller-rabin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "brorand": "^1.0.1"
+      }
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "dev": true
+    },
+    "mime-db": {
+      "version": "1.37.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
+      "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
+    },
+    "mime-types": {
+      "version": "2.1.21",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
+      "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
+      "requires": {
+        "mime-db": "~1.37.0"
+      }
+    },
+    "mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true
+    },
+    "mimic-response": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+      "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
+    },
+    "mini-css-extract-plugin": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz",
+      "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "normalize-url": "1.9.1",
+        "schema-utils": "^1.0.0",
+        "webpack-sources": "^1.1.0"
+      },
+      "dependencies": {
+        "normalize-url": {
+          "version": "1.9.1",
+          "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
+          "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
+          "dev": true,
+          "requires": {
+            "object-assign": "^4.0.1",
+            "prepend-http": "^1.0.0",
+            "query-string": "^4.1.0",
+            "sort-keys": "^1.0.0"
+          }
+        },
+        "prepend-http": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+          "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+          "dev": true
+        },
+        "query-string": {
+          "version": "4.3.4",
+          "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+          "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+          "dev": true,
+          "requires": {
+            "object-assign": "^4.1.0",
+            "strict-uri-encode": "^1.0.0"
+          }
+        },
+        "sort-keys": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+          "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+          "dev": true,
+          "requires": {
+            "is-plain-obj": "^1.0.0"
+          }
+        }
+      }
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "dev": true
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+      "dev": true
+    },
+    "minipass": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+      "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "minizlib": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
+      "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
+      "dev": true,
+      "requires": {
+        "minipass": "^2.9.0"
+      }
+    },
+    "mississippi": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+      "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+      "dev": true,
+      "requires": {
+        "concat-stream": "^1.5.0",
+        "duplexify": "^3.4.2",
+        "end-of-stream": "^1.1.0",
+        "flush-write-stream": "^1.0.0",
+        "from2": "^2.1.0",
+        "parallel-transform": "^1.1.0",
+        "pump": "^3.0.0",
+        "pumpify": "^1.3.3",
+        "stream-each": "^1.1.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "mixin-deep": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+      "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+      "dev": true,
+      "requires": {
+        "for-in": "^1.0.2",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "mkdirp": {
+      "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"
+      }
+    },
+    "moment": {
+      "version": "2.24.0",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+    },
+    "move-concurrently": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+      "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "copy-concurrently": "^1.0.0",
+        "fs-write-stream-atomic": "^1.0.8",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.3"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "dev": true
+    },
+    "multicast-dns": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
+      "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+      "dev": true,
+      "requires": {
+        "dns-packet": "^1.3.1",
+        "thunky": "^1.0.2"
+      }
+    },
+    "multicast-dns-service-types": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
+      "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
+      "dev": true
+    },
+    "mute-stream": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+      "dev": true
+    },
+    "nan": {
+      "version": "2.12.1",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
+      "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==",
+      "dev": true,
+      "optional": true
+    },
+    "nanomatch": {
+      "version": "1.2.13",
+      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "fragment-cache": "^0.2.1",
+        "is-windows": "^1.0.2",
+        "kind-of": "^6.0.2",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      }
+    },
+    "negotiator": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
+      "dev": true
+    },
+    "neo-async": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+      "dev": true
+    },
+    "ng2-charts": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-1.6.0.tgz",
+      "integrity": "sha512-9w0WH69x5/nuqC1og2WaY39NbaBqTGIP1+5gZaH7/KPN6UEPonNg/pYnsIVklLj1DWPWXKa8+XXIJZ1jy5nLxg==",
+      "requires": {
+        "chart.js": "^2.6.0"
+      },
+      "dependencies": {
+        "chart.js": {
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz",
+          "integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==",
+          "requires": {
+            "chartjs-color": "^2.1.0",
+            "moment": "^2.10.2"
+          }
+        }
+      }
+    },
+    "ng2-completer": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/ng2-completer/-/ng2-completer-2.0.8.tgz",
+      "integrity": "sha512-WzxJ4u3vAHsfBUaFCloEBoirPZrnDabtWEKyDok7dtjhS1ZvcbwQ4asdXuDO0hZ0T1QC66U/PwLhKfkG501hVg=="
+    },
+    "ngx-toastr": {
+      "version": "10.0.4",
+      "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-10.0.4.tgz",
+      "integrity": "sha512-iN+zr2Msae5wV334c1dytRhSYNdUz467jwv1NE91lMmllsMkpUzZlu8VdFCeTFt+/R4TWzz19xBRqhpp+OAuVA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
+    },
+    "node-fetch-npm": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
+      "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
+      "dev": true,
+      "requires": {
+        "encoding": "^0.1.11",
+        "json-parse-better-errors": "^1.0.0",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "node-forge": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
+      "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
+      "dev": true
+    },
+    "node-jose": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.3.tgz",
+      "integrity": "sha512-kupfi4uGWhRjnOmtie2T64cLge5a1TZyalEa8uWWWBgtKBcu41A4IGKpI9twZAxRnmviamEUQRK7LSyfFb2w8A==",
+      "requires": {
+        "base64url": "^3.0.1",
+        "es6-promise": "^4.2.6",
+        "lodash": "^4.17.11",
+        "long": "^4.0.0",
+        "node-forge": "^0.8.1",
+        "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "es6-promise": {
+          "version": "4.2.8",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+          "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+        },
+        "node-forge": {
+          "version": "0.8.5",
+          "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz",
+          "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q=="
+        }
+      }
+    },
+    "node-libs-browser": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+      "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+      "dev": true,
+      "requires": {
+        "assert": "^1.1.1",
+        "browserify-zlib": "^0.2.0",
+        "buffer": "^4.3.0",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "^1.0.0",
+        "crypto-browserify": "^3.11.0",
+        "domain-browser": "^1.1.1",
+        "events": "^3.0.0",
+        "https-browserify": "^1.0.0",
+        "os-browserify": "^0.3.0",
+        "path-browserify": "0.0.1",
+        "process": "^0.11.10",
+        "punycode": "^1.2.4",
+        "querystring-es3": "^0.2.0",
+        "readable-stream": "^2.3.3",
+        "stream-browserify": "^2.0.1",
+        "stream-http": "^2.7.2",
+        "string_decoder": "^1.0.0",
+        "timers-browserify": "^2.0.4",
+        "tty-browserify": "0.0.0",
+        "url": "^0.11.0",
+        "util": "^0.11.0",
+        "vm-browserify": "^1.0.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "dev": true
+        }
+      }
+    },
+    "node-releases": {
+      "version": "1.1.35",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.35.tgz",
+      "integrity": "sha512-JGcM/wndCN/2elJlU0IGdVEJQQnJwsLbgPCFd2pY7V0mxf17bZ0Gb/lgOtL29ZQhvEX5shnVhxQyZz3ex94N8w==",
+      "dev": true,
+      "requires": {
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "dependencies": {
+        "resolve": {
+          "version": "1.12.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+          "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+          "dev": true,
+          "requires": {
+            "path-parse": "^1.0.6"
+          }
+        }
+      }
+    },
+    "normalize-path": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+      "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+      "dev": true,
+      "requires": {
+        "remove-trailing-separator": "^1.0.1"
+      }
+    },
+    "normalize-range": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+      "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+      "dev": true
+    },
+    "normalize-url": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
+      "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==",
+      "requires": {
+        "prepend-http": "^2.0.0",
+        "query-string": "^5.0.1",
+        "sort-keys": "^2.0.0"
+      }
+    },
+    "npm-bundled": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz",
+      "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
+      "dev": true
+    },
+    "npm-package-arg": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz",
+      "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.6.0",
+        "osenv": "^0.1.5",
+        "semver": "^5.5.0",
+        "validate-npm-package-name": "^3.0.0"
+      }
+    },
+    "npm-packlist": {
+      "version": "1.4.6",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz",
+      "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==",
+      "dev": true,
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1"
+      }
+    },
+    "npm-pick-manifest": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz",
+      "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1",
+        "npm-package-arg": "^6.0.0",
+        "semver": "^5.4.1"
+      }
+    },
+    "npm-registry-fetch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz",
+      "integrity": "sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.3.4",
+        "bluebird": "^3.5.1",
+        "figgy-pudding": "^3.4.1",
+        "lru-cache": "^5.1.1",
+        "make-fetch-happen": "^5.0.0",
+        "npm-package-arg": "^6.1.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
+          "dev": true
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
+    "null-check": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz",
+      "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=",
+      "dev": true
+    },
+    "num2fraction": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+      "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+      "dev": true
+    },
+    "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
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object-component": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+      "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
+      "dev": true
+    },
+    "object-copy": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+      "dev": true,
+      "requires": {
+        "copy-descriptor": "^0.1.0",
+        "define-property": "^0.2.5",
+        "kind-of": "^3.0.3"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "object-hash": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
+      "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA=="
+    },
+    "object-inspect": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
+      "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
+      "dev": true
+    },
+    "object-is": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz",
+      "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=",
+      "dev": true
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true
+    },
+    "object-visit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.0"
+      }
+    },
+    "object.assign": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "function-bind": "^1.1.1",
+        "has-symbols": "^1.0.0",
+        "object-keys": "^1.0.11"
+      }
+    },
+    "object.getownpropertydescriptors": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+      "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "es-abstract": "^1.5.1"
+      }
+    },
+    "object.pick": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "obuf": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+      "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+      "dev": true
+    },
+    "oidc-token-hash": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-3.0.2.tgz",
+      "integrity": "sha512-dTzp80/y/da+um+i+sOucNqiPpwRL7M/xPwj7pH1TFA2/bqQ+OK2sJahSXbemEoLtPkHcFLyhLhLWZa9yW5+RA=="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "dev": true,
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+      "dev": true
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "onetime": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
+      "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^2.1.0"
+      }
+    },
+    "open": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz",
+      "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==",
+      "dev": true,
+      "requires": {
+        "is-wsl": "^1.1.0"
+      }
+    },
+    "openid-client": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-2.5.0.tgz",
+      "integrity": "sha512-t3hFD7xEoW1U25RyBcRFaL19fGGs6hNVTysq9pgmiltH0IVUPzH/bQV9w24pM5Q7MunnGv2/5XjIru6BQcWdxg==",
+      "requires": {
+        "base64url": "^3.0.0",
+        "got": "^8.3.2",
+        "lodash": "^4.17.11",
+        "lru-cache": "^5.1.1",
+        "node-jose": "^1.1.0",
+        "object-hash": "^1.3.1",
+        "oidc-token-hash": "^3.0.1",
+        "p-any": "^1.1.0"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+        }
+      }
+    },
+    "opn": {
+      "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"
+      }
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+      "dev": true,
+      "requires": {
+        "minimist": "~0.0.1",
+        "wordwrap": "~0.0.2"
+      },
+      "dependencies": {
+        "wordwrap": {
+          "version": "0.0.3",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+          "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+          "dev": true
+        }
+      }
+    },
+    "original": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
+      "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
+      "dev": true,
+      "requires": {
+        "url-parse": "^1.4.3"
+      }
+    },
+    "os-browserify": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+      "dev": true
+    },
+    "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
+    },
+    "os-locale": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+      "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+      "dev": true,
+      "requires": {
+        "execa": "^1.0.0",
+        "lcid": "^2.0.0",
+        "mem": "^4.0.0"
+      }
+    },
+    "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
+    },
+    "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"
+      }
+    },
+    "p-any": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/p-any/-/p-any-1.1.0.tgz",
+      "integrity": "sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g==",
+      "requires": {
+        "p-some": "^2.0.0"
+      }
+    },
+    "p-cancelable": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
+      "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ=="
+    },
+    "p-defer": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+      "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+      "dev": true
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+    },
+    "p-is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+      "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+      "dev": true
+    },
+    "p-limit": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+      "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+      "dev": true,
+      "requires": {
+        "p-try": "^2.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+      "dev": true,
+      "requires": {
+        "p-limit": "^2.0.0"
+      }
+    },
+    "p-map": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+      "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+      "dev": true
+    },
+    "p-retry": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
+      "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
+      "dev": true,
+      "requires": {
+        "retry": "^0.12.0"
+      }
+    },
+    "p-some": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/p-some/-/p-some-2.0.1.tgz",
+      "integrity": "sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY=",
+      "requires": {
+        "aggregate-error": "^1.0.0"
+      }
+    },
+    "p-timeout": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
+      "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
+      "requires": {
+        "p-finally": "^1.0.0"
+      }
+    },
+    "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
+    },
+    "pacote": {
+      "version": "9.5.5",
+      "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.5.tgz",
+      "integrity": "sha512-jAEP+Nqj4kyMWyNpfTU/Whx1jA7jEc5cCOlurm0/0oL+v8TAp1QSsK83N7bYe+2bEdFzMAtPG5TBebjzzGV0cA==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.3",
+        "cacache": "^12.0.2",
+        "figgy-pudding": "^3.5.1",
+        "get-stream": "^4.1.0",
+        "glob": "^7.1.3",
+        "infer-owner": "^1.0.4",
+        "lru-cache": "^5.1.1",
+        "make-fetch-happen": "^5.0.0",
+        "minimatch": "^3.0.4",
+        "minipass": "^2.3.5",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "normalize-package-data": "^2.4.0",
+        "npm-package-arg": "^6.1.0",
+        "npm-packlist": "^1.1.12",
+        "npm-pick-manifest": "^2.2.3",
+        "npm-registry-fetch": "^4.0.0",
+        "osenv": "^0.1.5",
+        "promise-inflight": "^1.0.1",
+        "promise-retry": "^1.1.1",
+        "protoduck": "^5.0.1",
+        "rimraf": "^2.6.2",
+        "safe-buffer": "^5.1.2",
+        "semver": "^5.6.0",
+        "ssri": "^6.0.1",
+        "tar": "^4.4.8",
+        "unique-filename": "^1.1.1",
+        "which": "^1.3.1"
+      },
+      "dependencies": {
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        },
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "npm-pick-manifest": {
+          "version": "2.2.3",
+          "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz",
+          "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==",
+          "dev": true,
+          "requires": {
+            "figgy-pudding": "^3.5.1",
+            "npm-package-arg": "^6.0.0",
+            "semver": "^5.4.1"
+          }
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "pako": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz",
+      "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==",
+      "dev": true
+    },
+    "parallel-transform": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+      "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+      "dev": true,
+      "requires": {
+        "cyclist": "^1.0.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.1.5"
+      }
+    },
+    "parse-asn1": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+      "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+      "dev": true,
+      "requires": {
+        "asn1.js": "^4.0.0",
+        "browserify-aes": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.0",
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "parse-json": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.3.1",
+        "json-parse-better-errors": "^1.0.1"
+      }
+    },
+    "parse5": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+      "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==",
+      "dev": true
+    },
+    "parseqs": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+      "dev": true,
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseuri": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+      "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+      "dev": true,
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+      "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
+      "dev": true
+    },
+    "pascalcase": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+      "dev": true
+    },
+    "path-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+      "dev": true
+    },
+    "path-dirname": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+      "dev": true
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
+    "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="
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+      "dev": true
+    },
+    "path-type": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+      "dev": true,
+      "requires": {
+        "pify": "^3.0.0"
+      }
+    },
+    "pbkdf2": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "dev": true,
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
+    "pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "dev": true
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "dev": true,
+      "requires": {
+        "pinkie": "^2.0.0"
+      }
+    },
+    "pkg-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+      "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+      "dev": true,
+      "requires": {
+        "find-up": "^3.0.0"
+      }
+    },
+    "portfinder": {
+      "version": "1.0.24",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz",
+      "integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==",
+      "dev": true,
+      "requires": {
+        "async": "^1.5.2",
+        "debug": "^2.2.0",
+        "mkdirp": "0.5.x"
+      },
+      "dependencies": {
+        "async": {
+          "version": "1.5.2",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+          "dev": true
+        }
+      }
+    },
+    "posix-character-classes": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+      "dev": true
+    },
+    "postcss": {
+      "version": "7.0.17",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz",
+      "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.4.2",
+        "source-map": "^0.6.1",
+        "supports-color": "^6.1.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "postcss-import": {
+      "version": "12.0.1",
+      "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz",
+      "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==",
+      "dev": true,
+      "requires": {
+        "postcss": "^7.0.1",
+        "postcss-value-parser": "^3.2.3",
+        "read-cache": "^1.0.0",
+        "resolve": "^1.1.7"
+      },
+      "dependencies": {
+        "postcss-value-parser": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+          "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
+          "dev": true
+        }
+      }
+    },
+    "postcss-load-config": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz",
+      "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==",
+      "dev": true,
+      "requires": {
+        "cosmiconfig": "^5.0.0",
+        "import-cwd": "^2.0.0"
+      }
+    },
+    "postcss-loader": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz",
+      "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "postcss": "^7.0.0",
+        "postcss-load-config": "^2.0.0",
+        "schema-utils": "^1.0.0"
+      }
+    },
+    "postcss-value-parser": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz",
+      "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
+      "dev": true
+    },
+    "prepend-http": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+      "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
+    },
+    "private": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+      "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
+      "dev": true
+    },
+    "process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+      "dev": true
+    },
+    "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=="
+    },
+    "promise": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "asap": "~2.0.3"
+      }
+    },
+    "promise-inflight": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+      "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+      "dev": true
+    },
+    "promise-retry": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
+      "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
+      "dev": true,
+      "requires": {
+        "err-code": "^1.0.0",
+        "retry": "^0.10.0"
+      },
+      "dependencies": {
+        "retry": {
+          "version": "0.10.1",
+          "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+          "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
+          "dev": true
+        }
+      }
+    },
+    "protoduck": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz",
+      "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
+      "dev": true,
+      "requires": {
+        "genfun": "^5.0.0"
+      }
+    },
+    "protractor": {
+      "version": "5.4.2",
+      "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.2.tgz",
+      "integrity": "sha512-zlIj64Cr6IOWP7RwxVeD8O4UskLYPoyIcg0HboWJL9T79F1F0VWtKkGTr/9GN6BKL+/Q/GmM7C9kFVCfDbP5sA==",
+      "dev": true,
+      "requires": {
+        "@types/q": "^0.0.32",
+        "@types/selenium-webdriver": "^3.0.0",
+        "blocking-proxy": "^1.0.0",
+        "browserstack": "^1.5.1",
+        "chalk": "^1.1.3",
+        "glob": "^7.0.3",
+        "jasmine": "2.8.0",
+        "jasminewd2": "^2.1.0",
+        "optimist": "~0.6.0",
+        "q": "1.4.1",
+        "saucelabs": "^1.5.0",
+        "selenium-webdriver": "3.6.0",
+        "source-map-support": "~0.4.0",
+        "webdriver-js-extender": "2.1.0",
+        "webdriver-manager": "^12.0.6"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "del": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+          "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+          "dev": true,
+          "requires": {
+            "globby": "^5.0.0",
+            "is-path-cwd": "^1.0.0",
+            "is-path-in-cwd": "^1.0.0",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0",
+            "rimraf": "^2.2.8"
+          }
+        },
+        "globby": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+          "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+          "dev": true,
+          "requires": {
+            "array-union": "^1.0.1",
+            "arrify": "^1.0.0",
+            "glob": "^7.0.3",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        },
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        },
+        "source-map-support": {
+          "version": "0.4.18",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
+          "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+          "dev": true,
+          "requires": {
+            "source-map": "^0.5.6"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        },
+        "webdriver-manager": {
+          "version": "12.1.1",
+          "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.1.tgz",
+          "integrity": "sha512-L9TEQmZs6JbMMRQI1w60mfps265/NCr0toYJl7p/R2OAk6oXAfwI6jqYP7EWae+d7Ad2S2Aj4+rzxoSjqk3ZuA==",
+          "dev": true,
+          "requires": {
+            "adm-zip": "^0.4.9",
+            "chalk": "^1.1.1",
+            "del": "^2.2.0",
+            "glob": "^7.0.3",
+            "ini": "^1.3.4",
+            "minimist": "^1.2.0",
+            "q": "^1.4.1",
+            "request": "^2.87.0",
+            "rimraf": "^2.5.2",
+            "semver": "^5.3.0",
+            "xml2js": "^0.4.17"
+          }
+        }
+      }
+    },
+    "proxy-addr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+      "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+      "dev": true,
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.0"
+      }
+    },
+    "prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+      "dev": true
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "psl": {
+      "version": "1.1.31",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
+      "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
+    },
+    "public-encrypt": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "parse-asn1": "^5.0.0",
+        "randombytes": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "pumpify": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+      "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+      "dev": true,
+      "requires": {
+        "duplexify": "^3.6.0",
+        "inherits": "^2.0.3",
+        "pump": "^2.0.0"
+      },
+      "dependencies": {
+        "pump": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+          "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+          "dev": true,
+          "requires": {
+            "end-of-stream": "^1.1.0",
+            "once": "^1.3.1"
+          }
+        }
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "q": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz",
+      "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=",
+      "dev": true
+    },
+    "qjobs": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
+      "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+    },
+    "query-string": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
+      "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
+      "requires": {
+        "decode-uri-component": "^0.2.0",
+        "object-assign": "^4.1.0",
+        "strict-uri-encode": "^1.0.0"
+      }
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+      "dev": true
+    },
+    "querystring-es3": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+      "dev": true
+    },
+    "querystringify": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+      "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
+      "dev": true
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "randomfill": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.0.5",
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "range-parser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+      "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
+      "dev": true
+    },
+    "raw-body": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+      "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+      "dev": true,
+      "requires": {
+        "bytes": "3.0.0",
+        "http-errors": "1.6.3",
+        "iconv-lite": "0.4.23",
+        "unpipe": "1.0.0"
+      }
+    },
+    "raw-loader": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz",
+      "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.1.0",
+        "schema-utils": "^2.0.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "schema-utils": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.4.1.tgz",
+          "integrity": "sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==",
+          "dev": true,
+          "requires": {
+            "ajv": "^6.10.2",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "read-cache": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+      "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=",
+      "dev": true,
+      "requires": {
+        "pify": "^2.3.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        }
+      }
+    },
+    "read-package-json": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.0.tgz",
+      "integrity": "sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.1",
+        "graceful-fs": "^4.1.2",
+        "json-parse-better-errors": "^1.0.1",
+        "normalize-package-data": "^2.0.0",
+        "slash": "^1.0.0"
+      }
+    },
+    "read-package-tree": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz",
+      "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
+      "dev": true,
+      "requires": {
+        "read-package-json": "^2.0.0",
+        "readdir-scoped-modules": "^1.0.0",
+        "util-promisify": "^2.1.0"
+      }
+    },
+    "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==",
+      "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"
+      }
+    },
+    "readdir-scoped-modules": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
+      "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
+      "dev": true,
+      "requires": {
+        "debuglog": "^1.0.1",
+        "dezalgo": "^1.0.0",
+        "graceful-fs": "^4.1.2",
+        "once": "^1.3.0"
+      }
+    },
+    "readdirp": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+      "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "micromatch": "^3.1.10",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+      "requires": {
+        "resolve": "^1.1.6"
+      }
+    },
+    "reflect-metadata": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
+      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
+      "dev": true
+    },
+    "regenerate": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+      "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+      "dev": true
+    },
+    "regenerate-unicode-properties": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz",
+      "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==",
+      "dev": true,
+      "requires": {
+        "regenerate": "^1.4.0"
+      }
+    },
+    "regenerator-runtime": {
+      "version": "0.13.3",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+      "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
+      "dev": true
+    },
+    "regenerator-transform": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
+      "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==",
+      "dev": true,
+      "requires": {
+        "private": "^0.1.6"
+      }
+    },
+    "regex-not": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "regexp.prototype.flags": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz",
+      "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2"
+      }
+    },
+    "regexpu-core": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+      "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+      "dev": true,
+      "requires": {
+        "regenerate": "^1.2.1",
+        "regjsgen": "^0.2.0",
+        "regjsparser": "^0.1.4"
+      }
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+      "dev": true
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+      "dev": true,
+      "requires": {
+        "jsesc": "~0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        }
+      }
+    },
+    "remove-trailing-separator": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+      "dev": true
+    },
+    "repeat-element": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+      "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+      "dev": true
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+      "dev": true
+    },
+    "repeating": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+      "dev": true,
+      "requires": {
+        "is-finite": "^1.0.0"
+      }
+    },
+    "request": {
+      "version": "2.88.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.0",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.4.3",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true
+    },
+    "require-main-filename": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+      "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+      "dev": true
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+      "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs="
+    },
+    "resolve-cwd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+      "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+      "dev": true,
+      "requires": {
+        "resolve-from": "^3.0.0"
+      }
+    },
+    "resolve-from": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+      "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+      "dev": true
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+      "dev": true
+    },
+    "responselike": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
+      "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
+      "requires": {
+        "lowercase-keys": "^1.0.0"
+      }
+    },
+    "restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "ret": {
+      "version": "0.1.15",
+      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true
+    },
+    "retry": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
+      "dev": true
+    },
+    "rfdc": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz",
+      "integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "run-async": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+      "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+      "dev": true,
+      "requires": {
+        "is-promise": "^2.1.0"
+      }
+    },
+    "run-queue": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+      "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1"
+      }
+    },
+    "rxjs": {
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
+      "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "rxjs-compat": {
+      "version": "6.3.3",
+      "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.3.3.tgz",
+      "integrity": "sha512-caGN7ixiabHpOofginKEquuHk7GgaCrC7UpUQ9ZqGp80tMc68msadOeP/2AKy2R4YJsT1+TX5GZCtxO82qWkyA=="
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safe-regex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+      "dev": true,
+      "requires": {
+        "ret": "~0.1.10"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "sass": {
+      "version": "1.22.9",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.9.tgz",
+      "integrity": "sha512-FzU1X2V8DlnqabrL4u7OBwD2vcOzNMongEJEx3xMEhWY/v26FFR3aG0hyeu2T965sfR0E9ufJwmG+Qjz78vFPQ==",
+      "dev": true,
+      "requires": {
+        "chokidar": ">=2.0.0 <4.0.0"
+      }
+    },
+    "sass-loader": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.2.0.tgz",
+      "integrity": "sha512-h8yUWaWtsbuIiOCgR9fd9c2lRXZ2uG+h8Dzg/AGNj+Hg/3TO8+BBAW9mEP+mh8ei+qBKqSJ0F1FLlYjNBc61OA==",
+      "dev": true,
+      "requires": {
+        "clone-deep": "^4.0.1",
+        "loader-utils": "^1.0.1",
+        "neo-async": "^2.5.0",
+        "pify": "^4.0.1",
+        "semver": "^5.5.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        }
+      }
+    },
+    "saucelabs": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz",
+      "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==",
+      "dev": true,
+      "requires": {
+        "https-proxy-agent": "^2.2.1"
+      }
+    },
+    "sax": {
+      "version": "0.5.8",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
+      "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=",
+      "dev": true
+    },
+    "schema-utils": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+      "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.1.0",
+        "ajv-errors": "^1.0.0",
+        "ajv-keywords": "^3.1.0"
+      }
+    },
+    "select-hose": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+      "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
+      "dev": true
+    },
+    "selenium-webdriver": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz",
+      "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==",
+      "dev": true,
+      "requires": {
+        "jszip": "^3.1.3",
+        "rimraf": "^2.5.4",
+        "tmp": "0.0.30",
+        "xml2js": "^0.4.17"
+      },
+      "dependencies": {
+        "tmp": {
+          "version": "0.0.30",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz",
+          "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=",
+          "dev": true,
+          "requires": {
+            "os-tmpdir": "~1.0.1"
+          }
+        }
+      }
+    },
+    "selfsigned": {
+      "version": "1.10.7",
+      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
+      "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
+      "dev": true,
+      "requires": {
+        "node-forge": "0.9.0"
+      }
+    },
+    "semver": {
+      "version": "5.5.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
+      "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
+    },
+    "semver-dsl": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
+      "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=",
+      "dev": true,
+      "requires": {
+        "semver": "^5.3.0"
+      }
+    },
+    "semver-intersect": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz",
+      "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==",
+      "dev": true,
+      "requires": {
+        "semver": "^5.0.0"
+      }
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "http-errors": {
+          "version": "1.7.3",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
+          "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
+          "dev": true,
+          "requires": {
+            "depd": "~1.1.2",
+            "inherits": "2.0.4",
+            "setprototypeof": "1.1.1",
+            "statuses": ">= 1.5.0 < 2",
+            "toidentifier": "1.0.0"
+          }
+        },
+        "inherits": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+          "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        },
+        "range-parser": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+          "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+          "dev": true
+        },
+        "setprototypeof": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+          "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+          "dev": true
+        },
+        "statuses": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+          "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+          "dev": true
+        }
+      }
+    },
+    "serialize-javascript": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
+      "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
+      "dev": true
+    },
+    "serve-index": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+      "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.4",
+        "batch": "0.6.1",
+        "debug": "2.6.9",
+        "escape-html": "~1.0.3",
+        "http-errors": "~1.6.2",
+        "mime-types": "~2.1.17",
+        "parseurl": "~1.3.2"
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "dev": true,
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      },
+      "dependencies": {
+        "parseurl": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+          "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+          "dev": true
+        }
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "set-value": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+      "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+      "dev": true
+    },
+    "setprototypeof": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+      "dev": true
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "shallow-clone": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^6.0.2"
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+    },
+    "shelljs": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
+      "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
+      "requires": {
+        "glob": "^7.0.0",
+        "interpret": "^1.0.0",
+        "rechoir": "^0.6.2"
+      }
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+    },
+    "slash": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+      "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+      "dev": true
+    },
+    "smart-buffer": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz",
+      "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==",
+      "dev": true
+    },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "dev": true,
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "socket.io": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz",
+      "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
+      "dev": true,
+      "requires": {
+        "debug": "~3.1.0",
+        "engine.io": "~3.2.0",
+        "has-binary2": "~1.0.2",
+        "socket.io-adapter": "~1.1.0",
+        "socket.io-client": "2.1.1",
+        "socket.io-parser": "~3.2.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "socket.io-adapter": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
+      "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=",
+      "dev": true
+    },
+    "socket.io-client": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz",
+      "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==",
+      "dev": true,
+      "requires": {
+        "backo2": "1.0.2",
+        "base64-arraybuffer": "0.1.5",
+        "component-bind": "1.0.0",
+        "component-emitter": "1.2.1",
+        "debug": "~3.1.0",
+        "engine.io-client": "~3.2.0",
+        "has-binary2": "~1.0.2",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "object-component": "0.0.3",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "socket.io-parser": "~3.2.0",
+        "to-array": "0.1.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "socket.io-parser": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
+      "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
+      "dev": true,
+      "requires": {
+        "component-emitter": "1.2.1",
+        "debug": "~3.1.0",
+        "isarray": "2.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+          "dev": true
+        }
+      }
+    },
+    "sockjs": {
+      "version": "0.3.19",
+      "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
+      "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
+      "dev": true,
+      "requires": {
+        "faye-websocket": "^0.10.0",
+        "uuid": "^3.0.1"
+      }
+    },
+    "sockjs-client": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz",
+      "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.2.5",
+        "eventsource": "^1.0.7",
+        "faye-websocket": "~0.11.1",
+        "inherits": "^2.0.3",
+        "json3": "^3.3.2",
+        "url-parse": "^1.4.3"
+      },
+      "dependencies": {
+        "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"
+          }
+        },
+        "faye-websocket": {
+          "version": "0.11.3",
+          "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
+          "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
+          "dev": true,
+          "requires": {
+            "websocket-driver": ">=0.5.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "socks": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz",
+      "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==",
+      "dev": true,
+      "requires": {
+        "ip": "^1.1.5",
+        "smart-buffer": "4.0.2"
+      }
+    },
+    "socks-proxy-agent": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
+      "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "~4.2.1",
+        "socks": "~2.3.2"
+      }
+    },
+    "sort-keys": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+      "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+      "requires": {
+        "is-plain-obj": "^1.0.0"
+      }
+    },
+    "source-list-map": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+      "dev": true
+    },
+    "source-map": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+      "dev": true
+    },
+    "source-map-loader": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz",
+      "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==",
+      "dev": true,
+      "requires": {
+        "async": "^2.5.0",
+        "loader-utils": "^1.1.0"
+      }
+    },
+    "source-map-resolve": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+      "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+      "dev": true,
+      "requires": {
+        "atob": "^2.1.1",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "source-map-support": {
+      "version": "0.5.9",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
+      "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "source-map-url": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+      "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+      "dev": true
+    },
+    "sourcemap-codec": {
+      "version": "1.4.6",
+      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
+      "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==",
+      "dev": true
+    },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
+    },
+    "spdy": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz",
+      "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.0",
+        "handle-thing": "^2.0.0",
+        "http-deceiver": "^1.2.7",
+        "select-hose": "^2.0.0",
+        "spdy-transport": "^3.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "spdy-transport": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+      "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.0",
+        "detect-node": "^2.0.4",
+        "hpack.js": "^2.1.6",
+        "obuf": "^1.1.2",
+        "readable-stream": "^3.0.6",
+        "wbuf": "^1.7.3"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
+          "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
+      }
+    },
+    "speed-measure-webpack-plugin": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz",
+      "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.1"
+      }
+    },
+    "split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.0"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
+    "sshpk": {
+      "version": "1.16.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz",
+      "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==",
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "ssri": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+      "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "static-extend": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+      "dev": true,
+      "requires": {
+        "define-property": "^0.2.5",
+        "object-copy": "^0.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "statuses": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+      "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+      "dev": true
+    },
+    "stream-browserify": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+      "dev": true,
+      "requires": {
+        "inherits": "~2.0.1",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "stream-each": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+      "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "stream-http": {
+      "version": "2.8.3",
+      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+      "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+      "dev": true,
+      "requires": {
+        "builtin-status-codes": "^3.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.3.6",
+        "to-arraybuffer": "^1.0.0",
+        "xtend": "^4.0.0"
+      }
+    },
+    "stream-shift": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+      "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+      "dev": true
+    },
+    "streamroller": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
+      "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==",
+      "dev": true,
+      "requires": {
+        "date-format": "^1.2.0",
+        "debug": "^3.1.0",
+        "mkdirp": "^0.5.1",
+        "readable-stream": "^2.3.0"
+      },
+      "dependencies": {
+        "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"
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        }
+      }
+    },
+    "strict-uri-encode": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+      "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
+    },
+    "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"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "string.prototype.trimleft": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
+      "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "function-bind": "^1.1.1"
+      }
+    },
+    "string.prototype.trimright": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
+      "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "function-bind": "^1.1.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==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+    },
+    "style-loader": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz",
+      "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.2.3",
+        "schema-utils": "^2.0.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "schema-utils": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.4.1.tgz",
+          "integrity": "sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==",
+          "dev": true,
+          "requires": {
+            "ajv": "^6.10.2",
+            "ajv-keywords": "^3.4.1"
+          }
+        }
+      }
+    },
+    "stylus": {
+      "version": "0.54.5",
+      "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz",
+      "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=",
+      "dev": true,
+      "requires": {
+        "css-parse": "1.7.x",
+        "debug": "*",
+        "glob": "7.0.x",
+        "mkdirp": "0.5.x",
+        "sax": "0.5.x",
+        "source-map": "0.1.x"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.0.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
+          "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.2",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "source-map": {
+          "version": "0.1.43",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+          "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
+          "dev": true,
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
+    "stylus-loader": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz",
+      "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.0.2",
+        "lodash.clonedeep": "^4.5.0",
+        "when": "~3.6.x"
+      }
+    },
+    "supports-color": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+      "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "symbol-observable": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+      "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+      "dev": true
+    },
+    "tapable": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+      "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+      "dev": true
+    },
+    "tar": {
+      "version": "4.4.13",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
+      "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
+      "dev": true,
+      "requires": {
+        "chownr": "^1.1.1",
+        "fs-minipass": "^1.2.5",
+        "minipass": "^2.8.6",
+        "minizlib": "^1.2.1",
+        "mkdirp": "^0.5.0",
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.3"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "terser": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-4.1.4.tgz",
+      "integrity": "sha512-+ZwXJvdSwbd60jG0Illav0F06GDJF0R4ydZ21Q3wGAFKoBGyJGo34F63vzJHgvYxc1ukOtIjvwEvl9MkjzM6Pg==",
+      "dev": true,
+      "requires": {
+        "commander": "^2.20.0",
+        "source-map": "~0.6.1",
+        "source-map-support": "~0.5.12"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.20.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
+          "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        },
+        "source-map-support": {
+          "version": "0.5.13",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+          "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+          "dev": true,
+          "requires": {
+            "buffer-from": "^1.0.0",
+            "source-map": "^0.6.0"
+          }
+        }
+      }
+    },
+    "terser-webpack-plugin": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
+      "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
+      "dev": true,
+      "requires": {
+        "cacache": "^12.0.2",
+        "find-cache-dir": "^2.1.0",
+        "is-wsl": "^1.1.0",
+        "schema-utils": "^1.0.0",
+        "serialize-javascript": "^1.7.0",
+        "source-map": "^0.6.1",
+        "terser": "^4.1.2",
+        "webpack-sources": "^1.4.0",
+        "worker-farm": "^1.7.0"
+      },
+      "dependencies": {
+        "find-cache-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+          "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+          "dev": true,
+          "requires": {
+            "commondir": "^1.0.1",
+            "make-dir": "^2.0.0",
+            "pkg-dir": "^3.0.0"
+          }
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      }
+    },
+    "thunky": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz",
+      "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==",
+      "dev": true
+    },
+    "timed-out": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+      "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
+    },
+    "timers-browserify": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+      "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+      "dev": true,
+      "requires": {
+        "setimmediate": "^1.0.4"
+      }
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "to-array": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+      "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
+      "dev": true
+    },
+    "to-arraybuffer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+      "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+      "dev": true
+    },
+    "to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+      "dev": true
+    },
+    "to-object-path": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "to-regex": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "regex-not": "^1.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1"
+      }
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+      "dev": true
+    },
+    "tough-cookie": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+      "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+      "requires": {
+        "psl": "^1.1.24",
+        "punycode": "^1.4.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        }
+      }
+    },
+    "tree-kill": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz",
+      "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==",
+      "dev": true
+    },
+    "trim-right": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+      "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+      "dev": true
+    },
+    "ts-node": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
+      "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+      "dev": true,
+      "requires": {
+        "arrify": "^1.0.0",
+        "buffer-from": "^1.1.0",
+        "diff": "^3.1.0",
+        "make-error": "^1.1.1",
+        "minimist": "^1.2.0",
+        "mkdirp": "^0.5.1",
+        "source-map-support": "^0.5.6",
+        "yn": "^2.0.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "tslib": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+      "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
+    },
+    "tslint": {
+      "version": "5.11.0",
+      "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz",
+      "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "^6.22.0",
+        "builtin-modules": "^1.1.1",
+        "chalk": "^2.3.0",
+        "commander": "^2.12.1",
+        "diff": "^3.2.0",
+        "glob": "^7.1.1",
+        "js-yaml": "^3.7.0",
+        "minimatch": "^3.0.4",
+        "resolve": "^1.3.2",
+        "semver": "^5.3.0",
+        "tslib": "^1.8.0",
+        "tsutils": "^2.27.2"
+      },
+      "dependencies": {
+        "resolve": {
+          "version": "1.10.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+          "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
+          "dev": true,
+          "requires": {
+            "path-parse": "^1.0.6"
+          }
+        }
+      }
+    },
+    "tsutils": {
+      "version": "2.29.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+      "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.8.1"
+      }
+    },
+    "tty-browserify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+      "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+      "dev": true
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+    },
+    "type-fest": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz",
+      "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==",
+      "dev": true
+    },
+    "type-is": {
+      "version": "1.6.16",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+      "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+      "dev": true,
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.18"
+      }
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "typescript": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
+      "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
+      "dev": true
+    },
+    "uglify-js": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
+      "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "commander": "~2.20.0",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.20.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+          "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
+          "dev": true,
+          "optional": true
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
+    "ultron": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
+      "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
+      "dev": true
+    },
+    "underscore": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
+      "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
+    },
+    "unicode-canonical-property-names-ecmascript": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+      "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
+      "dev": true
+    },
+    "unicode-match-property-ecmascript": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+      "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+      "dev": true,
+      "requires": {
+        "unicode-canonical-property-names-ecmascript": "^1.0.4",
+        "unicode-property-aliases-ecmascript": "^1.0.4"
+      }
+    },
+    "unicode-match-property-value-ecmascript": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz",
+      "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==",
+      "dev": true
+    },
+    "unicode-property-aliases-ecmascript": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz",
+      "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==",
+      "dev": true
+    },
+    "union-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+      "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^0.4.3"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "set-value": {
+          "version": "0.4.3",
+          "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+          "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+          "dev": true,
+          "requires": {
+            "extend-shallow": "^2.0.1",
+            "is-extendable": "^0.1.1",
+            "is-plain-object": "^2.0.1",
+            "to-object-path": "^0.3.0"
+          }
+        }
+      }
+    },
+    "unique-filename": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+      "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+      "dev": true,
+      "requires": {
+        "unique-slug": "^2.0.0"
+      }
+    },
+    "unique-slug": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+      "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+      "dev": true,
+      "requires": {
+        "imurmurhash": "^0.1.4"
+      }
+    },
+    "universal-analytics": {
+      "version": "0.4.20",
+      "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz",
+      "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.0.0",
+        "request": "^2.88.0",
+        "uuid": "^3.0.0"
+      },
+      "dependencies": {
+        "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"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "dev": true
+    },
+    "unset-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+      "dev": true,
+      "requires": {
+        "has-value": "^0.3.1",
+        "isobject": "^3.0.0"
+      },
+      "dependencies": {
+        "has-value": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+          "dev": true,
+          "requires": {
+            "get-value": "^2.0.3",
+            "has-values": "^0.1.4",
+            "isobject": "^2.0.0"
+          },
+          "dependencies": {
+            "isobject": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+              "dev": true,
+              "requires": {
+                "isarray": "1.0.0"
+              }
+            }
+          }
+        },
+        "has-values": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+          "dev": true
+        }
+      }
+    },
+    "upath": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
+      "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
+      "dev": true
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+      "dev": true
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "dev": true,
+      "requires": {
+        "punycode": "1.3.2",
+        "querystring": "0.2.0"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+          "dev": true
+        }
+      }
+    },
+    "url-parse": {
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+      "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+      "dev": true,
+      "requires": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "url-parse-lax": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
+      "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
+      "requires": {
+        "prepend-http": "^2.0.0"
+      }
+    },
+    "url-to-options": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
+      "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k="
+    },
+    "use": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+      "dev": true
+    },
+    "useragent": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+      "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
+      "dev": true,
+      "requires": {
+        "lru-cache": "4.1.x",
+        "tmp": "0.0.x"
+      }
+    },
+    "util": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+      "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "2.0.3"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "util-promisify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz",
+      "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
+      "dev": true,
+      "requires": {
+        "object.getownpropertydescriptors": "^2.0.3"
+      }
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "dev": true
+    },
+    "uuid": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+      "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "validate-npm-package-name": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+      "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+      "dev": true,
+      "requires": {
+        "builtins": "^1.0.3"
+      }
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "dev": true
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "vm-browserify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz",
+      "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
+      "dev": true
+    },
+    "void-elements": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+      "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
+      "dev": true
+    },
+    "watchpack": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
+      "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+      "dev": true,
+      "requires": {
+        "chokidar": "^2.0.2",
+        "graceful-fs": "^4.1.2",
+        "neo-async": "^2.5.0"
+      }
+    },
+    "wbuf": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+      "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+      "dev": true,
+      "requires": {
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "webdriver-js-extender": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz",
+      "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==",
+      "dev": true,
+      "requires": {
+        "@types/selenium-webdriver": "^3.0.0",
+        "selenium-webdriver": "^3.0.1"
+      }
+    },
+    "webpack": {
+      "version": "4.39.2",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.2.tgz",
+      "integrity": "sha512-AKgTfz3xPSsEibH00JfZ9sHXGUwIQ6eZ9tLN8+VLzachk1Cw2LVmy+4R7ZiwTa9cZZ15tzySjeMui/UnSCAZhA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.8.5",
+        "@webassemblyjs/helper-module-context": "1.8.5",
+        "@webassemblyjs/wasm-edit": "1.8.5",
+        "@webassemblyjs/wasm-parser": "1.8.5",
+        "acorn": "^6.2.1",
+        "ajv": "^6.10.2",
+        "ajv-keywords": "^3.4.1",
+        "chrome-trace-event": "^1.0.2",
+        "enhanced-resolve": "^4.1.0",
+        "eslint-scope": "^4.0.3",
+        "json-parse-better-errors": "^1.0.2",
+        "loader-runner": "^2.4.0",
+        "loader-utils": "^1.2.3",
+        "memory-fs": "^0.4.1",
+        "micromatch": "^3.1.10",
+        "mkdirp": "^0.5.1",
+        "neo-async": "^2.6.1",
+        "node-libs-browser": "^2.2.1",
+        "schema-utils": "^1.0.0",
+        "tapable": "^1.1.3",
+        "terser-webpack-plugin": "^1.4.1",
+        "watchpack": "^1.6.0",
+        "webpack-sources": "^1.4.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.10.2",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+          "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^2.0.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        }
+      }
+    },
+    "webpack-core": {
+      "version": "0.6.9",
+      "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz",
+      "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=",
+      "dev": true,
+      "requires": {
+        "source-list-map": "~0.1.7",
+        "source-map": "~0.4.1"
+      },
+      "dependencies": {
+        "source-list-map": {
+          "version": "0.1.8",
+          "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz",
+          "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.4.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+          "dev": true,
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
+    "webpack-dev-middleware": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz",
+      "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==",
+      "dev": true,
+      "requires": {
+        "memory-fs": "^0.4.1",
+        "mime": "^2.4.2",
+        "range-parser": "^1.2.1",
+        "webpack-log": "^2.0.0"
+      },
+      "dependencies": {
+        "mime": {
+          "version": "2.4.4",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+          "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
+          "dev": true
+        },
+        "range-parser": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+          "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+          "dev": true
+        }
+      }
+    },
+    "webpack-dev-server": {
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.8.0.tgz",
+      "integrity": "sha512-Hs8K9yI6pyMvGkaPTeTonhD6JXVsigXDApYk9JLW4M7viVBspQvb1WdAcWxqtmttxNW4zf2UFLsLNe0y87pIGQ==",
+      "dev": true,
+      "requires": {
+        "ansi-html": "0.0.7",
+        "bonjour": "^3.5.0",
+        "chokidar": "^2.1.6",
+        "compression": "^1.7.4",
+        "connect-history-api-fallback": "^1.6.0",
+        "debug": "^4.1.1",
+        "del": "^4.1.1",
+        "express": "^4.17.1",
+        "html-entities": "^1.2.1",
+        "http-proxy-middleware": "^0.19.1",
+        "import-local": "^2.0.0",
+        "internal-ip": "^4.3.0",
+        "ip": "^1.1.5",
+        "is-absolute-url": "^3.0.0",
+        "killable": "^1.0.1",
+        "loglevel": "^1.6.3",
+        "opn": "^5.5.0",
+        "p-retry": "^3.0.1",
+        "portfinder": "^1.0.21",
+        "schema-utils": "^1.0.0",
+        "selfsigned": "^1.10.4",
+        "semver": "^6.3.0",
+        "serve-index": "^1.9.1",
+        "sockjs": "0.3.19",
+        "sockjs-client": "1.3.0",
+        "spdy": "^4.0.1",
+        "strip-ansi": "^3.0.1",
+        "supports-color": "^6.1.0",
+        "url": "^0.11.0",
+        "webpack-dev-middleware": "^3.7.0",
+        "webpack-log": "^2.0.0",
+        "ws": "^6.2.1",
+        "yargs": "12.0.5"
+      },
+      "dependencies": {
+        "chokidar": {
+          "version": "2.1.8",
+          "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+          "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+          "dev": true,
+          "requires": {
+            "anymatch": "^2.0.0",
+            "async-each": "^1.0.1",
+            "braces": "^2.3.2",
+            "fsevents": "^1.2.7",
+            "glob-parent": "^3.1.0",
+            "inherits": "^2.0.3",
+            "is-binary-path": "^1.0.0",
+            "is-glob": "^4.0.0",
+            "normalize-path": "^3.0.0",
+            "path-is-absolute": "^1.0.0",
+            "readdirp": "^2.2.1",
+            "upath": "^1.1.1"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "normalize-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+          "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+          "dev": true
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "upath": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+          "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+          "dev": true
+        },
+        "ws": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+          "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+          "dev": true,
+          "requires": {
+            "async-limiter": "~1.0.0"
+          }
+        }
+      }
+    },
+    "webpack-log": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
+      "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+      "dev": true,
+      "requires": {
+        "ansi-colors": "^3.0.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "webpack-merge": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz",
+      "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.5"
+      }
+    },
+    "webpack-sources": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+      "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+      "dev": true,
+      "requires": {
+        "source-list-map": "^2.0.0",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "webpack-subresource-integrity": {
+      "version": "1.1.0-rc.6",
+      "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz",
+      "integrity": "sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w==",
+      "dev": true,
+      "requires": {
+        "webpack-core": "^0.6.8"
+      }
+    },
+    "websocket-driver": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
+      "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
+      "dev": true,
+      "requires": {
+        "http-parser-js": ">=0.4.0 <0.4.11",
+        "safe-buffer": ">=5.1.0",
+        "websocket-extensions": ">=0.1.1"
+      }
+    },
+    "websocket-extensions": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+      "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+      "dev": true
+    },
+    "when": {
+      "version": "3.6.4",
+      "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz",
+      "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=",
+      "dev": true
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+      "dev": true
+    },
+    "worker-farm": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+      "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+      "dev": true,
+      "requires": {
+        "errno": "~0.1.7"
+      }
+    },
+    "worker-plugin": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-3.2.0.tgz",
+      "integrity": "sha512-W5nRkw7+HlbsEt3qRP6MczwDDISjiRj2GYt9+bpe8A2La00TmJdwzG5bpdMXhRt1qcWmwAvl1TiKaHRa+XDS9Q==",
+      "dev": true,
+      "requires": {
+        "loader-utils": "^1.1.0"
+      }
+    },
+    "wrap-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "ws": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
+      "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
+      "dev": true,
+      "requires": {
+        "async-limiter": "~1.0.0",
+        "safe-buffer": "~5.1.0",
+        "ultron": "~1.1.0"
+      }
+    },
+    "xml2js": {
+      "version": "0.4.19",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+      "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+      "dev": true,
+      "requires": {
+        "sax": ">=0.6.0",
+        "xmlbuilder": "~9.0.1"
+      },
+      "dependencies": {
+        "sax": {
+          "version": "1.2.4",
+          "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+          "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+          "dev": true
+        }
+      }
+    },
+    "xmlbuilder": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+      "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
+      "dev": true
+    },
+    "xmlhttprequest-ssl": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
+      "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
+      "dev": true
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "dev": true
+    },
+    "y18n": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+      "dev": true
+    },
+    "yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+      "dev": true
+    },
+    "yargs": {
+      "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": "^1.2.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^1.0.1",
+        "os-locale": "^3.0.0",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^1.0.1",
+        "set-blocking": "^2.0.0",
+        "string-width": "^2.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^3.2.1 || ^4.0.0",
+        "yargs-parser": "^11.1.1"
+      }
+    },
+    "yargs-parser": {
+      "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": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    },
+    "yeast": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+      "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
+      "dev": true
+    },
+    "yn": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+      "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+      "dev": true
+    },
+    "zone.js": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz",
+      "integrity": "sha512-GkPiJL8jifSrKReKaTZ5jkhrMEgXbXYC+IPo1iquBjayRa0q86w3Dipjn8b415jpitMExe9lV8iTsv8tk3DGag=="
+    }
+  }
+}
diff --git a/dashboard/webapp-frontend/package.json b/dashboard/webapp-frontend/package.json
new file mode 100644 (file)
index 0000000..9f2cc86
--- /dev/null
@@ -0,0 +1,71 @@
+{
+  "name": "dash-app",
+  "version": "0.0.0",
+  "scripts": {
+    "ng": "ng",
+    "start": "ng serve --proxy-config proxy.conf.json",
+    "build": "ng build",
+    "test": "ng test",
+    "lint": "ng lint",
+    "e2e": "ng e2e"
+  },
+  "private": true,
+  "dependencies": {
+    "@angular/animations": "^8.2.9",
+    "@angular/cdk": "^7.3.7",
+    "@angular/common": "^8.2.9",
+    "@angular/compiler": "^8.2.9",
+    "@angular/core": "^8.2.9",
+    "@angular/flex-layout": "^7.0.0-beta.19",
+    "@angular/forms": "^8.2.9",
+    "@angular/material": "~7.2.0",
+    "@angular/platform-browser": "^8.2.9",
+    "@angular/platform-browser-dynamic": "^8.2.9",
+    "@angular/router": "^8.2.9",
+    "@fortawesome/fontawesome-free": "^5.9.0",
+    "@kubernetes/client-node": "^0.10.3",
+    "@material/radio": "^2.3.0",
+    "@types/chart.js": "^2.7.54",
+    "angular-bootstrap-md": "^7.5.4",
+    "angular6-json-schema-form": "^7.3.0",
+    "bootstrap": "^4.3.1",
+    "chart.js": "^2.8.0",
+    "core-js": "^2.6.9",
+    "hammerjs": "^2.0.8",
+    "lodash-es": "^4.17.15",
+    "material-design-icons": "^3.0.1",
+    "ng2-charts": "^1.6.0",
+    "ng2-completer": "^2.0.8",
+    "ngx-toastr": "^10.0.4",
+    "rxjs": "6.5.3",
+    "rxjs-compat": "6.3.3",
+    "tslib": "^1.10.0",
+    "zone.js": "~0.9.1"
+  },
+  "devDependencies": {
+    "@angular-devkit/build-angular": "~0.803.8",
+    "@angular/cli": "^8.3.8",
+    "@angular/compiler-cli": "^8.2.9",
+    "@angular/language-service": "^8.2.9",
+    "@types/jasmine": "^2.8.16",
+    "@types/jasminewd2": "~2.0.3",
+    "@types/node": "~8.9.4",
+    "codelyzer": "^5.0.1",
+    "jasmine-core": "~2.99.1",
+    "jasmine-spec-reporter": "~4.2.1",
+    "karma": "~3.1.1",
+    "karma-chrome-launcher": "~2.2.0",
+    "karma-coverage-istanbul-reporter": "^2.0.5",
+    "karma-jasmine": "~1.1.2",
+    "karma-jasmine-html-reporter": "^0.2.2",
+    "protractor": "~5.4.0",
+    "ts-node": "~7.0.0",
+    "tslint": "~5.11.0",
+    "typescript": "~3.5.3"
+  },
+  "comments": {
+    "dependencies": {
+      "rxjs": "Its version needs to be same with rxjs-compat, otherwise apparently build is failing"
+    }
+  }
+}
diff --git a/dashboard/webapp-frontend/pom.xml b/dashboard/webapp-frontend/pom.xml
new file mode 100644 (file)
index 0000000..fd75241
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--<![CDATA[
+========================LICENSE_START=================================
+O-RAN-SC
+%%
+Copyright (C) 2019 AT&T Intellectual Property
+%%
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+========================LICENSE_END===================================
+]]>-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.o-ran-sc.portal.ric-dashboard</groupId>
+               <artifactId>ric-dash-parent</artifactId>
+               <version>1.2.5-SNAPSHOT</version>
+       </parent>
+       <artifactId>ric-dash-fe</artifactId>
+       <name>RIC Dashboard Webapp frontend</name>
+       <build>
+               <plugins>
+                       <plugin>
+                               <!-- Most configuration and all execution is inherited -->
+                               <groupId>org.codehaus.mojo</groupId>
+                               <artifactId>license-maven-plugin</artifactId>
+                               <configuration>
+                                       <roots>
+                                               <root>e2e</root>
+                                               <root>src</root>
+                                       </roots>
+                                       <excludes>
+                                               <exclude>**/*.json</exclude>
+                                       </excludes>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>com.github.eirslett</groupId>
+                               <artifactId>frontend-maven-plugin</artifactId>
+                               <version>1.3</version>
+                               <configuration>
+                                       <nodeVersion>v10.15.3</nodeVersion>
+                                       <npmVersion>6.7.0</npmVersion>
+                                       <workingDirectory>.</workingDirectory>
+                               </configuration>
+                               <executions>
+                                       <execution>
+                                               <id>install node and npm</id>
+                                               <goals>
+                                                       <goal>install-node-and-npm</goal>
+                                               </goals>
+                                       </execution>
+                                       <execution>
+                                               <id>npm install</id>
+                                               <goals>
+                                                       <goal>npm</goal>
+                                               </goals>
+                                       </execution>
+                                       <execution>
+                                               <id>npm run build</id>
+                                               <goals>
+                                                       <goal>npm</goal>
+                                               </goals>
+                                               <configuration>
+                                                       <arguments>run build</arguments>
+                                               </configuration>
+                                       </execution>
+                                       <execution>
+                                               <id>prod</id>
+                                               <goals>
+                                                       <goal>npm</goal>
+                                               </goals>
+                                               <configuration>
+                                                       <arguments>run-script build</arguments>
+                                               </configuration>
+                                               <phase>generate-resources</phase>
+                                       </execution>
+                               </executions>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-clean-plugin</artifactId>
+                               <configuration>
+                                       <filesets>
+                                               <fileset>
+                                                       <directory>${project.basedir}</directory>
+                                                       <includes>
+                                                               <include>**/node_modules/**</include>
+                                                               <include>dist/**</include>
+                                                               <include>node/**</include>
+                                                       </includes>
+                                                       <followSymlinks>false</followSymlinks>
+                                               </fileset>
+                                               <fileset>
+                                                       <directory>node</directory>
+                                               </fileset>
+                                       </filesets>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+</project>
diff --git a/dashboard/webapp-frontend/proxy.conf.json b/dashboard/webapp-frontend/proxy.conf.json
new file mode 100644 (file)
index 0000000..d3d6eee
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "/api": {
+     "target": "http://localhost:8080",
+     "secure": false
+  } 
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.animations.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.animations.ts
new file mode 100644 (file)
index 0000000..c00f877
--- /dev/null
@@ -0,0 +1,28 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { animate, state, style, transition, trigger } from '@angular/animations';
+
+export const AppControlAnimations = {
+  messageTrigger: trigger('messageExpand', [
+    state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),
+    state('expanded', style({ height: '*' })),
+  ])
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.html b/dashboard/webapp-frontend/src/app/app-control/app-control.component.html
new file mode 100644 (file)
index 0000000..ec802f4
--- /dev/null
@@ -0,0 +1,100 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<div class="app-control__section">
+  <h3 class="rd-global-page-title">xApp Control</h3>
+
+  <table mat-table [dataSource]="dataSource" matSort multiTemplateDataRows class="app-control-table mat-elevation-z8">
+
+    <ng-container matColumnDef="xapp">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> App Name </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.xapp}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="name">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> Instance Name</mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.instance.name}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="status">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> Status </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.instance.status}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="ip" >
+      <mat-header-cell *matHeaderCellDef mat-sort-header> IP </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.instance.ip}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="port">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> Port </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.instance.port}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="action">
+      <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
+      <!-- click on button should not expand/collapse the row -->
+      <mat-cell *matCellDef="let element" (click)="$event.stopPropagation()">
+      <button mat-icon-button (click)="controlApp(element)">
+          <mat-icon matTooltip="Adjust settings">settings</mat-icon>
+        </button>
+        <button mat-icon-button (click)="onUndeployApp(element)">
+          <mat-icon matTooltip="Undeploy app">cloud_download</mat-icon>
+        </button>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="expandedDetail">
+      <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
+        <div [@messageExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
+          <div>
+            txMessages:
+          </div>
+          <li *ngFor="let rxmessage of element.instance.rxMessages">
+            <span>{{rxmessage}}</span>
+          </li>
+          <div>
+            rxMessages:
+          </div>
+          <li *ngFor="let txmessage of element.instance.txMessages">
+            <span>{{txmessage}}</span>
+          </li>
+        </div>
+      </td>
+    </ng-container>
+
+    <ng-container matColumnDef="noRecordsFound">
+      <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+    </ng-container>
+
+    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
+    <mat-row *matRowDef="let element; columns: displayedColumns;"
+      [class.example-expanded-row]="expandedElement === element"
+      (click)="expandedElement = expandedElement === element ? null : element"></mat-row>
+    <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="message-row"></tr>
+    <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': dataSource.rowCount > 0}"></mat-footer-row>
+
+  </table>
+
+  <div class="spinner-container" *ngIf="dataSource.loading$ | async">
+    <mat-spinner diameter=50></mat-spinner>
+  </div>
+
+</div>
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.scss b/dashboard/webapp-frontend/src/app/app-control/app-control.component.scss
new file mode 100644 (file)
index 0000000..232562f
--- /dev/null
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+ .app-control__section {
+}
+
+
+.spinner-container {
+  height: 100px;
+  width: 100px;
+}
+
+.spinner-container mat-spinner {
+  margin: 0 auto 0 auto;
+}
+
+.app-control-table {
+  width: 100%;
+  min-height: 150px;
+  margin-top: 10px;
+  background-color: transparent;
+}
+
+tr.message-row {
+  height: 0;
+}
+
+.display-none {
+  display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.spec.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.component.spec.ts
new file mode 100644 (file)
index 0000000..6648d81
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AppControlComponent } from './app-control.component';
+
+describe('AppControlComponent', () => {
+  let component: AppControlComponent;
+  let fixture: ComponentFixture<AppControlComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ AppControlComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(AppControlComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.component.ts
new file mode 100644 (file)
index 0000000..07b931d
--- /dev/null
@@ -0,0 +1,103 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { MatSort } from '@angular/material/sort';
+import { Router } from '@angular/router';
+import { XappControlRow } from '../interfaces/app-mgr.types';
+import { AppMgrService } from '../services/app-mgr/app-mgr.service';
+import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { AppControlAnimations } from './app-control.animations';
+import { AppControlDataSource } from './app-control.datasource';
+import { finalize } from 'rxjs/operators';
+
+@Component({
+  selector: 'rd-app-control',
+  templateUrl: './app-control.component.html',
+  styleUrls: ['./app-control.component.scss'],
+  animations: [AppControlAnimations.messageTrigger]
+})
+export class AppControlComponent implements OnInit {
+
+  displayedColumns: string[] = ['xapp', 'name', 'status', 'ip', 'port', 'action'];
+  dataSource: AppControlDataSource;
+  @ViewChild(MatSort, {static: true}) sort: MatSort;
+
+  constructor(
+    private appMgrSvc: AppMgrService,
+    private router: Router,
+    private confirmDialogService: ConfirmDialogService,
+    private errorDialogService: ErrorDialogService,
+    private loadingDialogService: LoadingDialogService,
+    private notificationService: NotificationService) { }
+
+  ngOnInit() {
+    this.dataSource = new AppControlDataSource(this.appMgrSvc, this.sort, this.notificationService);
+    this.dataSource.loadTable();
+  }
+
+  controlApp(app: XappControlRow): void {
+    // TODO: identify apps without hardcoding to names
+    const acAppPattern0 =  /[Aa][Dd][Mm][Ii][Nn]/;
+    const acAppPattern1 =  /[Aa][Dd][Mm][Ii][Ss]{2}[Ii][Oo][Nn]/;
+    const anrAppPattern0 = /ANR/;
+    const anrAppPattern1 = /[Aa][Uu][Tt][Oo][Mm][Aa][Tt][Ii][Cc]/;
+    const anrAppPattern2 = /[Nn][Ee][Ii][Gg][Hh][Bb][Oo][Rr]/;
+    if (acAppPattern0.test(app.xapp) || acAppPattern1.test(app.xapp)) {
+      this.router.navigate(['/ac']);
+    } else if (anrAppPattern0.test(app.xapp) || (anrAppPattern1.test(app.xapp) && anrAppPattern2.test(app.xapp))) {
+      this.router.navigate(['/anr']);
+    } else {
+      this.errorDialogService.displayError('No control available for ' + app.xapp + ' (yet)');
+    }
+  }
+
+  onUndeployApp(app: XappControlRow): void {
+    this.confirmDialogService.openConfirmDialog('Are you sure you want to undeploy App ' + app.xapp + '?')
+      .afterClosed().subscribe( (res: boolean) => {
+        if (res) {
+          this.loadingDialogService.startLoading("Undeploying " + app.xapp);
+          this.appMgrSvc.undeployXapp(app.xapp)
+            .pipe(
+              finalize(() => this.loadingDialogService.stopLoading())
+            )
+            .subscribe(
+            ( httpResponse: HttpResponse<Object>) => {
+              // Answers 204/No content on success
+              this.notificationService.success('App undeployed successfully!');
+              this.dataSource.loadTable();
+            },
+            ( (her: HttpErrorResponse) => {
+              // the error field should have an ErrorTransport object
+              let msg = her.message;
+              if (her.error && her.error.message) {
+                msg = her.error.message;
+              }
+              this.notificationService.warn('App undeploy failed: ' + msg);
+            })
+          );
+        }
+      });
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.datasource.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.datasource.ts
new file mode 100644 (file)
index 0000000..e97dc63
--- /dev/null
@@ -0,0 +1,134 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material/sort';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { XappControlRow, XMDeployedApp, XMXappInstance } from '../interfaces/app-mgr.types';
+import { AppMgrService } from '../services/app-mgr/app-mgr.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class AppControlDataSource extends DataSource<XappControlRow> {
+
+  private appControlSubject = new BehaviorSubject<XappControlRow[]>([]);
+
+  private loadingSubject = new BehaviorSubject<boolean>(false);
+
+  public loading$ = this.loadingSubject.asObservable();
+
+  public rowCount = 1; // hide footer during intial load
+
+  private emptyInstances: XMXappInstance =
+    { ip: null,
+      name: null,
+      port: null,
+      status: null,
+      rxMessages: [],
+      txMessages: [],
+    };
+
+  constructor(private appMgrSvc: AppMgrService,
+    private sort: MatSort,
+    private notificationService: NotificationService) {
+    super();
+  }
+
+  loadTable() {
+    this.loadingSubject.next(true);
+    this.appMgrSvc.getDeployed()
+      .pipe(
+        catchError( (her: HttpErrorResponse) => {
+          console.log('AppControlDataSource failed: ' + her.message);
+          this.notificationService.error('Failed to get applications: ' + her.message);
+          return of([]);
+        }),
+        finalize(() => this.loadingSubject.next(false))
+      )
+      .subscribe( (xApps: XMDeployedApp[]) => {
+        this.rowCount = xApps.length;
+        const flattenedApps = this.flatten(xApps);
+        this.appControlSubject.next(flattenedApps);
+      });
+  }
+
+  connect(collectionViewer: CollectionViewer): Observable<XappControlRow[]> {
+    const dataMutations = [
+      this.appControlSubject.asObservable(),
+      this.sort.sortChange
+    ];
+    return merge(...dataMutations).pipe(map(() => {
+      return this.getSortedData([...this.appControlSubject.getValue()]);
+    }));
+  }
+
+  disconnect(collectionViewer: CollectionViewer): void {
+    this.appControlSubject.complete();
+    this.loadingSubject.complete();
+  }
+
+  private flatten(allxappdata: XMDeployedApp[]): XappControlRow[]  {
+    const xAppInstances: XappControlRow[] = [];
+    for (const xapp of allxappdata) {
+      if (!xapp.instances) {
+        const row: XappControlRow = {
+          xapp: xapp.name,
+          instance: this.emptyInstances
+        };
+        xAppInstances.push(row);
+      } else {
+        for (const ins of xapp.instances) {
+          const row: XappControlRow = {
+            xapp: xapp.name,
+            instance: ins
+          };
+          xAppInstances.push(row);
+        }
+      }
+    }
+    return xAppInstances;
+  }
+
+  private getSortedData(data: XappControlRow[]) {
+    if (!this.sort.active || this.sort.direction === '') {
+      return data;
+    }
+
+    return data.sort((a, b) => {
+      const isAsc = this.sort.direction === 'asc';
+      switch (this.sort.active) {
+        case 'xapp': return compare(a.xapp, b.xapp, isAsc);
+        case 'name': return compare(a.instance.name, b.instance.name, isAsc);
+        case 'status': return compare(a.instance.status, b.instance.status, isAsc);
+        case 'ip': return compare(a.instance.ip, b.instance.ip, isAsc);
+        case 'port': return compare(a.instance.port, b.instance.port, isAsc);
+        default: return 0;
+      }
+    });
+  }
+}
+
+function compare(a: any, b: any, isAsc: boolean) {
+  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.html b/dashboard/webapp-frontend/src/app/control/control.component.html
new file mode 100644 (file)
index 0000000..e3258b6
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<div class="control__section">
+  <rd-ran-control></rd-ran-control>
+  <hr>
+  <rd-app-control></rd-app-control>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.scss b/dashboard/webapp-frontend/src/app/control/control.component.scss
new file mode 100644 (file)
index 0000000..f06d0ce
--- /dev/null
@@ -0,0 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+.control__section {
+  background-color: transparent;
+}
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.spec.ts b/dashboard/webapp-frontend/src/app/control/control.component.spec.ts
new file mode 100644 (file)
index 0000000..eb7d064
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ControlComponent } from './control.component';
+
+describe('ControlComponent', () => {
+  let component: ControlComponent;
+  let fixture: ComponentFixture<ControlComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ControlComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ControlComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.ts b/dashboard/webapp-frontend/src/app/control/control.component.ts
new file mode 100644 (file)
index 0000000..18545af
--- /dev/null
@@ -0,0 +1,34 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'rd-control',
+  templateUrl: './control.component.html',
+  styleUrls: ['./control.component.scss']
+})
+export class ControlComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.html b/dashboard/webapp-frontend/src/app/footer/footer.component.html
new file mode 100644 (file)
index 0000000..5dd1c36
--- /dev/null
@@ -0,0 +1,24 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<div class="copyright__text" [ngClass]="{'footer-dark': darkMode}">
+  Copyright (C) 2019 AT&T Intellectual Property. Licensed under the Apache License, Version 2.0.
+  <br/>
+  Version {{dashboardVersion}}
+</div>
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.scss b/dashboard/webapp-frontend/src/app/footer/footer.component.scss
new file mode 100644 (file)
index 0000000..e2456d7
--- /dev/null
@@ -0,0 +1,28 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+.copyright__text {
+  color: gray;
+  letter-spacing: 0.1rem;
+  font-size: 12px;
+}
+
+.footer-dark {
+  color: white;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.spec.ts b/dashboard/webapp-frontend/src/app/footer/footer.component.spec.ts
new file mode 100644 (file)
index 0000000..6f80297
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FooterComponent } from './footer.component';
+
+describe('FooterComponent', () => {
+  let component: FooterComponent;
+  let fixture: ComponentFixture<FooterComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ FooterComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FooterComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.ts b/dashboard/webapp-frontend/src/app/footer/footer.component.ts
new file mode 100644 (file)
index 0000000..e119d70
--- /dev/null
@@ -0,0 +1,49 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+import { DashboardSuccessTransport } from '../interfaces/dashboard.types';
+import { DashboardService } from '../services/dashboard/dashboard.service';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+  selector: 'rd-footer',
+  templateUrl: './footer.component.html',
+  styleUrls: ['./footer.component.scss']
+})
+
+/**
+ * Fetches the version on load for display in the footer
+ */
+export class FooterComponent implements OnInit {
+  darkMode: boolean;
+  dashboardVersion: string;
+
+  // Inject the service
+  constructor(private dashboardService: DashboardService,
+              public ui: UiService ) { }
+
+  ngOnInit() {
+    this.dashboardService.getVersion().subscribe((res: DashboardSuccessTransport) => this.dashboardVersion = res.data);
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/ac-xapp.types.ts b/dashboard/webapp-frontend/src/app/interfaces/ac-xapp.types.ts
new file mode 100644 (file)
index 0000000..125f72f
--- /dev/null
@@ -0,0 +1,33 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by the AC xApp
+
+export interface ACAdmissionIntervalControl {
+  enforce: boolean;
+  window_length: number;
+  blocking_rate: number;
+  trigger_threshold: number;
+}
+
+export interface ACAdmissionIntervalControlAck {
+  status: string;
+  message: string;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/anr-xapp.types.ts b/dashboard/webapp-frontend/src/app/interfaces/anr-xapp.types.ts
new file mode 100644 (file)
index 0000000..6b7db25
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by the ANR xApp
+
+export interface ANRGgNodeBTable {
+  gNodeBIds: Array<string>;
+}
+
+export interface ANRNeighborCellRelationTable {
+  ncrtRelations: Array<ANRNeighborCellRelation>;
+}
+
+export interface ANRNeighborCellRelation {
+  servingCellNrcgi: string;
+  neighborCellNrpci: string;
+  neighborCellNrcgi: string;
+  flagNoHo: boolean;
+  flagNoXn: boolean;
+  flagNoRemove: boolean;
+}
+
+export interface ANRNeighborCellRelationMod {
+  servingCellNrcgi: string;
+  neighborCellNrpci: string;
+  neighborCellNrcgi: string;
+  flagNoHo: boolean;
+  flagNoXn: boolean;
+  flagNoRemove: boolean;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/app-mgr.types.ts b/dashboard/webapp-frontend/src/app/interfaces/app-mgr.types.ts
new file mode 100644 (file)
index 0000000..064967c
--- /dev/null
@@ -0,0 +1,67 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by the App Manager
+
+export interface XMSubscription {
+  eventType: string;
+  id: string;
+  maxRetries: number;
+  retryTimer: number;
+  targetUrl: string;
+}
+
+/**
+ * Name is the only required field
+ */
+export interface XMXappInfo {
+  name: string;
+  configName?: string;
+  namespace?: string;
+  serviceName?: string;
+  imageRepo?: string;
+  hostname?: string;
+}
+
+export interface XMXappInstance {
+  ip: string;
+  name: string;
+  port: number;
+  status: string;
+  rxMessages: Array<string>;
+  txMessages: Array<string>;
+}
+
+export interface XMDeployableApp {
+  name: string;
+  version: string;
+}
+
+export interface XMDeployedApp {
+  name: string;
+  status: string;
+  version: string;
+  instances: Array<XMXappInstance>;
+}
+
+export interface XappControlRow {
+  xapp: string;
+  instance: XMXappInstance;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/dashboard.types.ts b/dashboard/webapp-frontend/src/app/interfaces/dashboard.types.ts
new file mode 100644 (file)
index 0000000..90dfd15
--- /dev/null
@@ -0,0 +1,57 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by Dashboard admin services
+
+export interface DashboardSuccessTransport {
+  status: number;
+  data: string;
+}
+
+export interface EcompRoleFunction {
+  name: string;
+  code: string;
+  type: string;
+  action: string;
+}
+
+export interface EcompRole {
+  id: number;
+  name: string;
+  [position: number]: EcompRoleFunction;
+}
+
+export interface EcompUser {
+  orgId?: number;
+  managerId?: string;
+  firstName?: string;
+  middleInitial?: string;
+  lastName?: string;
+  phone?: string;
+  email?: string;
+  hrid?: string;
+  orgUserId?: string;
+  orgCode?: string;
+  orgManagerUserId?: string;
+  jobTitle?: string;
+  loginId: string;
+  active: boolean;
+  [position: number]: EcompRole;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/e2-mgr.types.ts b/dashboard/webapp-frontend/src/app/interfaces/e2-mgr.types.ts
new file mode 100644 (file)
index 0000000..f303c14
--- /dev/null
@@ -0,0 +1,66 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by the E2 Manager
+
+export interface E2SetupRequest {
+  ranName: string;
+  ranIp: string;
+  ranPort: string;
+}
+
+export interface E2ErrorResponse {
+  errorCode: string;
+  errorMessage: string;
+}
+
+export interface E2NodebIdentityGlobalNbId {
+  nbId: string;
+  plmnId: string;
+}
+
+export interface E2NodebIdentity {
+  inventoryName: string;
+  globalNbId: E2NodebIdentityGlobalNbId;
+}
+
+export interface E2GetNodebResponse {
+  connectionStatus: string; // actually one-of, but model as string
+  enb: object; // don't model this until needed
+  failureType: string; // actually one-of, but model as string
+  gnb: object; // don't model this until needed
+  ip: string;
+  nodeType: string; // actually one-of, but model as string
+  port: number; // actually integer
+  ranName: string;
+  setupFailure: object; // don't model this until needed
+}
+
+export interface E2RanDetails {
+  nodebIdentity: E2NodebIdentity;
+  nodebStatus: E2GetNodebResponse;
+}
+
+export interface RanDialogFormData {
+  ranIp: string;
+  ranName: string;
+  ranPort: string;
+  ranType: string;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/policy.types.ts b/dashboard/webapp-frontend/src/app/interfaces/policy.types.ts
new file mode 100644 (file)
index 0000000..bc94af9
--- /dev/null
@@ -0,0 +1,38 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by the Policy Control
+
+export interface PolicyType {
+  policy_type_id: number;
+  name: string;
+  description: string;
+  create_schema: string;
+}
+
+export interface PolicyInstance {
+  instanceId: string;
+  instance: string;
+}
+
+export interface PolicyInstanceAck {
+  status: string;
+  message: string;
+}
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.html b/dashboard/webapp-frontend/src/app/main/main.component.html
new file mode 100644 (file)
index 0000000..f05fd50
--- /dev/null
@@ -0,0 +1,23 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  Modifications Copyright (C) 2019 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<div class = "main__container">
+  <rd-policy-card></rd-policy-card>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.scss b/dashboard/webapp-frontend/src/app/main/main.component.scss
new file mode 100644 (file)
index 0000000..aeb3b5d
--- /dev/null
@@ -0,0 +1,27 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+.main__container {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  grid-template-rows: repeat(auto-fill, 1fr);
+  align-items: center;
+  justify-items: center;
+  height: 100%;
+}
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.spec.ts b/dashboard/webapp-frontend/src/app/main/main.component.spec.ts
new file mode 100644 (file)
index 0000000..47f86b4
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MainComponent } from './main.component';
+
+describe('MainComponent', () => {
+  let component: MainComponent;
+  let fixture: ComponentFixture<MainComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ MainComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MainComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.ts b/dashboard/webapp-frontend/src/app/main/main.component.ts
new file mode 100644 (file)
index 0000000..8967a42
--- /dev/null
@@ -0,0 +1,33 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'rd-main',
+  templateUrl: './main.component.html',
+  styleUrls: ['./main.component.scss']
+})
+export class MainComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() { }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
new file mode 100644 (file)
index 0000000..d91c4a9
--- /dev/null
@@ -0,0 +1,33 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  Modifications Copyright (C) 2019 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<!-- browse icons at https://material.io/tools/icons/?style=baseline -->
+<mat-nav-list [ngClass]="{'dark': darkMode}">
+  <a mat-list-item routerLink="/" (click)="onSidenavClose()">
+    <mat-icon>home</mat-icon> <span class="nav-caption">Home</span>
+  </a>  
+  <a mat-list-item routerLink="/control" (click)="onSidenavClose()">
+    <mat-icon>settings</mat-icon><span class="nav-caption">Control</span>
+  </a>  
+  <a mat-list-item routerLink="/policy" (click)="onSidenavClose()">
+      <mat-icon>assignment</mat-icon> <span class="nav-caption">Policy</span>
+  </a>    
+</mat-nav-list>
diff --git a/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss
new file mode 100644 (file)
index 0000000..72c724e
--- /dev/null
@@ -0,0 +1,36 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+a {
+    text-decoration: none;
+    color: black;
+}
+
+.dark a {
+  color: white;
+}
+
+a:hover, a:active{
+    color: lightgray;
+}
+.nav-caption{
+    display: inline-block;
+    padding-left: 6px;
+}
diff --git a/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts
new file mode 100644 (file)
index 0000000..c0f05af
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit, Output, EventEmitter } from '@angular/core';
+import { UiService } from '../../services/ui/ui.service';
+
+@Component({
+  selector: 'rd-sidenav-list',
+  templateUrl: './sidenav-list.component.html',
+  styleUrls: ['./sidenav-list.component.scss']
+})
+export class SidenavListComponent implements OnInit {
+  darkMode: boolean;
+  @Output() sidenavClose = new EventEmitter();
+
+  constructor(public ui: UiService) { }
+
+  ngOnInit() {
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+  }
+
+  public onSidenavClose = () => {
+    this.sidenavClose.emit();
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.html b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.html
new file mode 100644 (file)
index 0000000..04d440c
--- /dev/null
@@ -0,0 +1,82 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<div>
+    <h3 class="rd-global-page-title">Policy Control</h3>
+
+    <table mat-table [dataSource]="policyTypesDataSource" matSort multiTemplateDataRows
+        class="policy-type-table mat-elevation-z8">
+
+        <ng-container matColumnDef="name">
+            <mat-header-cell *matHeaderCellDef mat-sort-header>Policy Type</mat-header-cell>
+            <mat-cell *matCellDef="let policyType"> 
+                 <mat-icon matTooltip="Properties">{{isInstancesShown(policyType)  ? 'expand_less' : 'expand_more'}}</mat-icon>
+                 {{getPolicyTypeName(policyType)}}
+            </mat-cell>
+        </ng-container>
+
+        <ng-container matColumnDef="description">
+            <mat-header-cell *matHeaderCellDef> Description </mat-header-cell>
+            <mat-cell *matCellDef="let policyType"> {{policyType.description}} </mat-cell>
+        </ng-container>
+
+        <ng-container matColumnDef="action">
+            <mat-header-cell class="action-cell" *matHeaderCellDef>Action </mat-header-cell>
+            <mat-cell class="action-cell" *matCellDef="let policyType" (click)="$event.stopPropagation()">               
+                <button mat-icon-button (click)="createPolicyInstance(policyType)">
+                    <mat-icon matTooltip="Create instance">add_box</mat-icon>
+                </button>
+            </mat-cell>
+        </ng-container>
+
+        <!-- =================== Policy instances for one type ======================== -->
+        <ng-container matColumnDef="instanceTableContainer">
+            <mat-cell *matCellDef="let policyType">
+                <rd-policy-instance 
+                [policyType]=policyType 
+                [expanded]=getObservable(policyType)>
+            </rd-policy-instance>
+            </mat-cell>
+        </ng-container>
+        <!-- ======= -->
+
+        <ng-container matColumnDef="noRecordsFound">
+            <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+        </ng-container>
+
+        <mat-header-row *matHeaderRowDef="['name', 'description', 'action']"></mat-header-row>
+        <mat-row *matRowDef="let policyType; columns: ['name', 'description', 'action']"
+            (click)="toggleListInstances(policyType)">
+        </mat-row>
+
+        <mat-row *matRowDef="let policyType; columns: ['instanceTableContainer'];"
+            [@detailExpand]="isInstancesShown(policyType) ? 'expanded' : 'collapsed'" style="overflow: hidden">
+        </mat-row>
+
+        <mat-footer-row *matFooterRowDef="['noRecordsFound']"
+            [ngClass]="{'display-none': policyTypesDataSource.rowCount > 0}">
+        </mat-footer-row>
+
+    </table>
+
+    <div class="spinner-container" *ngIf="policyTypesDataSource.loading$ | async">
+        <mat-spinner diameter="50"></mat-spinner>
+    </div>
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.scss b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.scss
new file mode 100644 (file)
index 0000000..f93e4ff
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+.spinner-container {
+  height: 100px;
+  width: 100px;
+}
+
+.spinner-container mat-spinner {
+  margin: 0 auto 0 auto;
+}
+
+.policy-type-table {
+  width: 100%;
+  min-height: 150px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  background-color: transparent;
+}
+
+.action-cell {
+      display: flex;
+      justify-content: flex-end;
+}
+
+.display-none {
+  display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.spec.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.spec.ts
new file mode 100644 (file)
index 0000000..7c8643a
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PolicyControlComponent } from './policy-control.component';
+
+describe('PolicyControlComponent', () => {
+  let component: PolicyControlComponent;
+  let fixture: ComponentFixture<PolicyControlComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ PolicyControlComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PolicyControlComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.ts
new file mode 100644 (file)
index 0000000..70b8c45
--- /dev/null
@@ -0,0 +1,115 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
+import { MatSort } from '@angular/material/sort';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+
+import { PolicyService } from '../services/policy/policy.service';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyTypeDataSource } from './policy-type.datasource';
+import { PolicyInstanceDataSource } from './policy-instance.datasource';
+import { getPolicyDialogProperties } from './policy-instance-dialog.component';
+import { PolicyInstanceDialogComponent } from './policy-instance-dialog.component';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { NotificationService } from '../services/ui/notification.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
+import { Subject } from 'rxjs';
+
+class PolicyTypeInfo {
+    constructor(public type: PolicyType, public isExpanded: boolean) { }
+
+    isExpandedObservers: Subject<boolean> = new Subject<boolean>();
+};
+
+@Component({
+    selector: 'rd-policy-control',
+    templateUrl: './policy-control.component.html',
+    styleUrls: ['./policy-control.component.scss'],
+    animations: [
+        trigger('detailExpand', [
+            state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })),
+            state('expanded', style({ height: '*', visibility: 'visible' })),
+            transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
+        ]),
+    ],
+})
+export class PolicyControlComponent implements OnInit {
+
+    policyTypesDataSource: PolicyTypeDataSource;
+    @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+    expandedTypes = new Map<string, PolicyTypeInfo>();
+
+    constructor(
+        private policySvc: PolicyService,
+        private dialog: MatDialog,
+        private errorDialogService: ErrorDialogService,
+        private notificationService: NotificationService,
+        private confirmDialogService: ConfirmDialogService) { }
+
+    ngOnInit() {
+        this.policyTypesDataSource = new PolicyTypeDataSource(this.policySvc, this.sort, this.notificationService);
+        this.policyTypesDataSource.loadTable();
+    }
+
+    createPolicyInstance(policyType: PolicyType): void {
+        const dialogRef = this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(policyType, null));
+        const info: PolicyTypeInfo = this.getPolicyTypeInfo(policyType);
+        dialogRef.afterClosed().subscribe(
+            (result: any) => {
+                info.isExpandedObservers.next(info.isExpanded);
+            }
+        );
+    }
+
+    toggleListInstances(policyType: PolicyType): void {
+        let info = this.getPolicyTypeInfo(policyType);
+        info.isExpanded = !info.isExpanded;
+        info.isExpandedObservers.next(info.isExpanded);
+    }
+
+    getPolicyTypeInfo(policyType: PolicyType): PolicyTypeInfo {
+        let info: PolicyTypeInfo = this.expandedTypes.get(policyType.name);
+        if (!info) {
+            info = new PolicyTypeInfo(policyType, false);
+            this.expandedTypes.set(policyType.name, info);
+        }
+        return info;
+    }
+
+    isInstancesShown(policyType: PolicyType): boolean {
+        return this.getPolicyTypeInfo(policyType).isExpanded;
+    }
+
+    getPolicyTypeName(type: PolicyType): string {
+        const schema = JSON.parse(type.create_schema);
+        if (schema.title) {
+            return schema.title;
+        }
+        return type.name;
+    }
+
+    getObservable(policyType: PolicyType): Subject<boolean> {
+        return this.getPolicyTypeInfo(policyType).isExpandedObservers;
+    }
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html
new file mode 100644 (file)
index 0000000..ad7ea49
--- /dev/null
@@ -0,0 +1,90 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<div class="text-muted logo" fxLayout="row" fxLayoutGap="50px">
+    <div *ngIf="policyInstanceId">{{policyInstanceId}}</div>
+</div>
+<div class="mat-elevation-z8 header row logo">
+    <div class="logo">
+        <img src="../../assets/oran-logo.png" width="30px" height="30px" style="position: relative; z-index: 50" />
+        <svg class="logo__icon" viewBox="150.3 22.2 400 50">
+            <text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85" font-size="30"
+                font-weight="600" letter-spacing=".1em" transform="translate(149 56)">
+                <tspan>Policy editor</tspan>
+                <tspan *ngIf="jsonSchemaObject.title"> {{this.jsonSchemaObject.title}}</tspan>
+                <tspan *ngIf="!jsonSchemaObject.title"> {{this.policyTypeName}}</tspan>
+            </text>
+        </svg>
+    </div>
+</div>
+
+<!--<div class="text-muted" *ngIf="jsonSchemaObject.description">{{jsonSchemaObject.description}}</div>-->
+
+<div fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-sm="column" fxLayoutAlign.lt-sm="flex-start center">
+    <mat-card class="card">
+        <h4 class="default-cursor" (click)="toggleVisible('form')">
+            <mat-icon matTooltip="Properties">{{isVisible.form ? 'expand_less' : 'expand_more'}}</mat-icon>
+            Properties
+        </h4>
+        <div *ngIf="isVisible.form" class="json-schema-form" [@expandSection]="true">
+            <div *ngIf="!formActive">{{jsonFormStatusMessage}}</div>
+
+            <json-schema-form *ngIf="formActive" loadExternalAssets="true" [form]="jsonSchemaObject"
+                [(data)]="jsonObject" [options]="jsonFormOptions" [framework]="'material-design'" [language]="'en'"
+                (onChanges)="onChanges($event)" (onSubmit)="onSubmit($event)" (isValid)="isValid($event)"
+                (validationErrors)="validationErrors($event)">
+            </json-schema-form>
+        </div>
+        <hr />
+        <button mat-raised-button (click)="this.onSubmit()" [disabled]="!this.formIsValid" class="submitBtn"
+            style="margin-right:10px">Submit</button>
+        <button mat-raised-button (click)="this.onClose()">Close</button>
+        <hr />
+        <h4 [class.text-danger]="!formIsValid && !isVisible.json" [class.default-cursor]="formIsValid || isVisible.json"
+            (click)="toggleVisible('json')">
+            <mat-icon matTooltip="Json">{{isVisible.json ? 'expand_less' : 'expand_more'}}</mat-icon>
+            Json
+        </h4>
+        <div *ngIf="isVisible.json" fxLayout="column" [@expandSection]="true">
+            <div>
+                <strong *ngIf="formIsValid || prettyValidationErrors" [class.text-muted]="formIsValid"
+                    [class.text-danger]="!formIsValid">
+                    {{formIsValid ? 'Json' : 'Not valid'}}
+                </strong>
+                <span *ngIf="!formIsValid && !prettyValidationErrors">Invalid form</span>
+                <span *ngIf="prettyValidationErrors">— errors:</span>
+                <div *ngIf="prettyValidationErrors" class="text-danger" [innerHTML]="prettyValidationErrors"></div>
+            </div>
+            <div>
+                <pre [class.data-good]="!prettyValidationErrors && prettyLiveFormData !== '{}'"
+                    [class.data-bad]="prettyValidationErrors">{{prettyLiveFormData}}
+                </pre>
+            </div>
+        </div>
+
+        <h4 class="default-cursor" (click)="toggleVisible('schema')">
+            <mat-icon matTooltip="Json Schema">{{isVisible.schema ? 'expand_less' : 'expand_more'}}</mat-icon>
+            Json Schema
+        </h4>
+        <div *ngIf="isVisible.schema" fxLayout="column" [@expandSection]="true">
+            <strong class="text-muted">Schema</strong>
+            <pre>{{schemaAsString}}</pre>
+        </div>
+    </mat-card>
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.scss b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.scss
new file mode 100644 (file)
index 0000000..7050020
--- /dev/null
@@ -0,0 +1,56 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+.header {
+    color: black;
+    background: linear-gradient(to right, white 0%, rgb(217, 216, 231) 100%);
+    font-size: 40px;
+    font-weight: 400;
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.logo {
+    margin-left: 10px;
+}
+
+.logo__text {
+    fill: #2B244D;
+}
+  
+.logo__text-dark {
+    fill: #ffffff;
+}
+
+.logo__icon {
+    height: 2rem;
+    margin-left: 1rem;
+}
+
+.submitBtn {
+    background-color: #4CAF50; /* Green */
+}
+
+.card {
+    height: 100%;
+    width: 100%;  
+    margin-left: 10px;
+    margin-right: 1px;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts
new file mode 100644 (file)
index 0000000..0f483d4
--- /dev/null
@@ -0,0 +1,214 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit, ViewChild, Inject, AfterViewInit, Self } from '@angular/core';
+import { MatMenuTrigger } from '@angular/material/menu';
+import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+import * as uuid from 'uuid';
+
+import { JsonPointer } from 'angular6-json-schema-form';
+import { PolicyService } from '../services/policy/policy.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { NotificationService } from './../services/ui/notification.service';
+
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyInstance } from '../interfaces/policy.types';
+
+@Component({
+    selector: 'rd-policy-instance-dialog',
+    templateUrl: './policy-instance-dialog.component.html',
+    styleUrls: ['./policy-instance-dialog.component.scss'],
+    animations: [
+        trigger('expandSection', [
+            state('in', style({ height: '*' })),
+            transition(':enter', [
+                style({ height: 0 }), animate(100),
+            ]),
+            transition(':leave', [
+                style({ height: '*' }),
+                animate(100, style({ height: 0 })),
+            ]),
+        ]),
+    ],
+})
+export class PolicyInstanceDialogComponent implements OnInit, AfterViewInit {
+
+    formActive = false;
+    isVisible = {
+        form: true,
+        json: false,
+        schema: false
+    };
+
+    jsonFormStatusMessage = 'Loading form...';
+    jsonSchemaObject: any = {};
+    jsonObject: any = {};
+
+
+    jsonFormOptions: any = {
+        addSubmit: false, // Add a submit button if layout does not have one
+        debug: false, // Don't show inline debugging information
+        loadExternalAssets: true, // Load external css and JavaScript for frameworks
+        returnEmptyFields: false, // Don't return values for empty input fields
+        setSchemaDefaults: true, // Always use schema defaults for empty fields
+        defautWidgetOptions: { feedback: true }, // Show inline feedback icons
+    };
+
+    liveFormData: any = {};
+    formValidationErrors: any;
+    formIsValid = false;
+
+
+    @ViewChild(MatMenuTrigger, { static: true }) menuTrigger: MatMenuTrigger;
+
+    public policyInstanceId: string;
+    public policyTypeName: string;
+    private policyTypeId: number;
+
+    constructor(
+        private dataService: PolicyService,
+        private errorService: ErrorDialogService,
+        private notificationService: NotificationService,
+        @Inject(MAT_DIALOG_DATA) private data,
+        private dialogRef: MatDialogRef<PolicyInstanceDialogComponent>) {
+        this.formActive = false;
+        this.policyInstanceId = this.data.instanceId;
+        this.policyTypeName = this.data.name;
+        this.policyTypeId = this.data.policyTypeId;
+        this.parseJson(data.createSchema, data.instanceJson);
+    }
+
+    ngOnInit() {
+        this.jsonFormStatusMessage = 'Init';
+        this.formActive = true;
+    }
+
+    ngAfterViewInit() {
+    }
+
+    onSubmit() {
+        if (this.policyInstanceId == null) {
+            this.policyInstanceId = uuid.v4();
+        }
+        const policyJson: string = this.prettyLiveFormData;
+        const self: PolicyInstanceDialogComponent = this;
+        this.dataService.putPolicy(this.policyTypeId, this.policyInstanceId, policyJson).subscribe(
+            {
+                next(value) {
+                    self.notificationService.success('Policy ' + self.policyTypeName + ':' + self.policyInstanceId + ' submitted');
+                },
+                error(error) {
+                    self.errorService.displayError('updatePolicy failed: ' + error.message);
+                },
+                complete() { }
+            });
+    }
+
+    onClose() {
+        this.dialogRef.close();
+    }
+
+    public onChanges(data: any) {
+        this.liveFormData = data;
+    }
+
+    get prettyLiveFormData() {
+        return JSON.stringify(this.liveFormData, null, 2);
+    }
+
+    get schemaAsString() {
+        return JSON.stringify(this.jsonSchemaObject, null, 2);
+    }
+
+    get jsonAsString() {
+        return JSON.stringify(this.jsonObject, null, 2);
+    }
+
+    isValid(isValid: boolean): void {
+        this.formIsValid = isValid;
+    }
+
+    validationErrors(data: any): void {
+        this.formValidationErrors = data;
+    }
+
+    get prettyValidationErrors() {
+        if (!this.formValidationErrors) { return null; }
+        const errorArray = [];
+        for (const error of this.formValidationErrors) {
+            const message = error.message;
+            const dataPathArray = JsonPointer.parse(error.dataPath);
+            if (dataPathArray.length) {
+                let field = dataPathArray[0];
+                for (let i = 1; i < dataPathArray.length; i++) {
+                    const key = dataPathArray[i];
+                    field += /^\d+$/.test(key) ? `[${key}]` : `.${key}`;
+                }
+                errorArray.push(`${field}: ${message}`);
+            } else {
+                errorArray.push(message);
+            }
+        }
+        return errorArray.join('<br>');
+    }
+
+    private parseJson(createSchema: string, instanceJson: string): void {
+        try {
+            this.jsonSchemaObject = JSON.parse(createSchema);
+            if (this.data.instanceJson != null) {
+                this.jsonObject = JSON.parse(instanceJson);
+            }
+        } catch (jsonError) {
+            this.jsonFormStatusMessage =
+                'Invalid JSON\n' +
+                'parser returned:\n\n' + jsonError;
+            return;
+        }
+    }
+
+    public toggleVisible(item: string) {
+        this.isVisible[item] = !this.isVisible[item];
+    }
+}
+
+export function getPolicyDialogProperties(policyType: PolicyType, instance: PolicyInstance): MatDialogConfig {
+    const policyTypeId = policyType.policy_type_id;
+    const createSchema = policyType.create_schema;
+    const instanceId = instance ? instance.instanceId : null;
+    const instanceJson = instance ? instance.instance : null;
+    const name = policyType.name;
+
+    return {
+        maxWidth: '1200px',
+        height: '1200px',
+        width: '900px',
+        role: 'dialog',
+        disableClose: false,
+        data: {
+            policyTypeId,
+            createSchema,
+            instanceId,
+            instanceJson,
+            name
+        }
+    };
+}
+
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.html b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.html
new file mode 100644 (file)
index 0000000..60bfab2
--- /dev/null
@@ -0,0 +1,57 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<table #table mat-table class="instances-table mat-elevation-z8" matSort  multiTemplateDataRows [dataSource]="instanceDataSource">
+
+    <ng-container matColumnDef="instanceId">
+        <mat-header-cell mat-sort-header *matHeaderCellDef >Instance</mat-header-cell>
+        <mat-cell *matCellDef="let element" (click)="modifyInstance(element)">{{element.instanceId}}
+        </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="action">
+        <mat-header-cell class="action-cell" *matHeaderCellDef>Action</mat-header-cell>
+        <mat-cell class="action-cell" *matCellDef="let instance">
+            <button mat-icon-button (click)="modifyInstance(instance)">
+                <mat-icon>edit</mat-icon>
+            </button>
+            <button mat-icon-button color="warn" (click)="deleteInstance(instance)">
+                <mat-icon>delete</mat-icon>
+            </button>
+        </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="noRecordsFound">
+        <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+    </ng-container>
+
+    <mat-header-row *matHeaderRowDef="['instanceId',  'action']"
+        [ngClass]="{'display-none': !this.hasInstances()}">
+    </mat-header-row>
+    <mat-row *matRowDef="let instance; columns: ['instanceId', 'action'];"></mat-row>
+
+    <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': this.hasInstances()}">
+    </mat-footer-row>
+
+</table>
+
+
+<div class="spinner-container" *ngIf="instanceDataSource.loading$ | async">
+    <mat-spinner diameter="50"></mat-spinner>
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.scss b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.scss
new file mode 100644 (file)
index 0000000..5c1d7c3
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+.instances-table {
+  width: 60%; ;
+  min-height: 150px;
+  min-width: 600px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  background-color:rgb(233, 233, 240);   
+}
+
+.action-cell {
+      display: flex; 
+      justify-content: flex-end;
+}
+
+.display-none {
+  display: none;
+}
+
+.spinner-container mat-spinner {
+  margin: 0 auto 0 auto;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.ts
new file mode 100644 (file)
index 0000000..3544275
--- /dev/null
@@ -0,0 +1,113 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { MatSort } from '@angular/material';
+import { Component, OnInit, ViewChild, Input, AfterViewInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyInstanceDataSource } from './policy-instance.datasource';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { PolicyService } from '../services/policy/policy.service';
+import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { PolicyInstanceDialogComponent } from './policy-instance-dialog.component';
+import { getPolicyDialogProperties } from './policy-instance-dialog.component';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+@Component({
+    selector: 'rd-policy-instance',
+    templateUrl: './policy-instance.component.html',
+    styleUrls: ['./policy-instance.component.scss']
+})
+
+
+export class PolicyInstanceComponent implements OnInit, AfterViewInit {
+    instanceDataSource: PolicyInstanceDataSource;
+    @Input() policyType: PolicyType;
+    @Input() expanded: Observable<boolean>;
+    @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+    constructor(
+        private policySvc: PolicyService,
+        private dialog: MatDialog,
+        private errorDialogService: ErrorDialogService,
+        private notificationService: NotificationService,
+        private confirmDialogService: ConfirmDialogService) {
+    }
+   
+    ngOnInit() {
+        this.instanceDataSource = new PolicyInstanceDataSource(this.policySvc, this.sort, this.notificationService, this.policyType);
+        this.expanded.subscribe((isExpanded: boolean) => this.onExpand(isExpanded));
+    }
+    
+    ngAfterViewInit() {
+        this.instanceDataSource.sort = this.sort;
+    }
+
+    private onExpand(isExpanded: boolean) {
+        if (isExpanded) {
+            this.instanceDataSource.loadTable();
+        }
+    }
+
+    modifyInstance(instance: PolicyInstance): void {
+        this.policySvc.getPolicy(this.policyType.policy_type_id, instance.instanceId).subscribe(
+            (refreshedJson: any) => {
+                instance.instance = JSON.stringify(refreshedJson);
+                this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance));
+            },
+            (httpError: HttpErrorResponse) => {
+                this.notificationService.error('Could not refresh instance ' + httpError.message);
+                this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance));
+            }
+        );
+    }
+
+    hasInstances(): boolean {
+        return this.instanceDataSource.rowCount > 0;
+    }
+
+    deleteInstance(instance: PolicyInstance): void {
+        this.confirmDialogService
+            .openConfirmDialog('Are you sure you want to delete this policy instance?')
+            .afterClosed().subscribe(
+                (res: any) => {
+                    if (res) {
+                        this.policySvc.deletePolicy(this.policyType.policy_type_id, instance.instanceId)
+                            .subscribe(
+                                (response: HttpResponse<Object>) => {
+                                    switch (response.status) {
+                                        case 200:
+                                            this.notificationService.success('Delete succeeded!');
+                                            this.instanceDataSource.loadTable();
+                                            break;
+                                        default:
+                                            this.notificationService.warn('Delete failed.');
+                                    }
+                                },
+                                (error: HttpErrorResponse) => {
+                                    this.errorDialogService.displayError(error.message);
+                                });
+                    }
+                });
+    }
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts
new file mode 100644 (file)
index 0000000..c74c9ab
--- /dev/null
@@ -0,0 +1,100 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { PolicyType } from '../interfaces/policy.types';
+
+export class PolicyInstanceDataSource extends DataSource<PolicyInstance> {
+
+    private policyInstanceSubject = new BehaviorSubject<PolicyInstance[]>([]);
+
+    private loadingSubject = new BehaviorSubject<boolean>(false);
+
+    public loading$ = this.loadingSubject.asObservable();
+
+    public rowCount = 1; // hide footer during intial load
+
+    constructor(
+        private policySvc: PolicyService,    
+        public sort: MatSort,
+        private notificationService: NotificationService, 
+        private policyType: PolicyType) {
+        super();
+    }
+
+    loadTable() {
+        this.loadingSubject.next(true);
+        this.policySvc.getPolicyInstances(this.policyType.policy_type_id)
+            .pipe(
+                catchError((her: HttpErrorResponse) => {
+                    this.notificationService.error('Failed to get policy instances: ' + her.message);
+                    return of([]);
+                }),
+                finalize(() => this.loadingSubject.next(false))
+            )
+            .subscribe((instances: PolicyInstance[]) => {
+                this.rowCount = instances.length;
+                this.policyInstanceSubject.next(instances);
+            });
+    }
+
+    connect(): Observable<PolicyInstance[]> {
+        const dataMutations = [
+            this.policyInstanceSubject.asObservable(),
+            this.sort.sortChange
+        ];
+        return merge(...dataMutations).pipe(map(() => {
+            return this.getSortedData([...this.policyInstanceSubject.getValue()]);
+        }));
+    }
+
+    disconnect(): void {
+        this.policyInstanceSubject.complete();
+        this.loadingSubject.complete();
+    }
+
+    private getSortedData(data: PolicyInstance[]) {
+        if (!this.sort || !this.sort.active || this.sort.direction === '') {
+            return data;
+        }
+
+        return data.sort((a, b) => {
+            const isAsc = this.sort.direction === 'asc';
+            switch (this.sort.active) {
+                case 'instanceId': return compare(a.instanceId, b.instanceId, isAsc);
+                default: return 0;
+            }
+        });
+    }
+}
+
+function compare(a: string, b: string, isAsc: boolean) {  
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-type.datasource.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-type.datasource.ts
new file mode 100644 (file)
index 0000000..1b2b93e
--- /dev/null
@@ -0,0 +1,97 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class PolicyTypeDataSource extends DataSource<PolicyType> {
+
+    private policyTypeSubject = new BehaviorSubject<PolicyType[]>([]);
+
+    private loadingSubject = new BehaviorSubject<boolean>(false);
+
+    public loading$ = this.loadingSubject.asObservable();
+
+    public rowCount = 1; // hide footer during intial load
+
+    constructor(private policySvc: PolicyService,
+        private sort: MatSort,
+        private notificationService: NotificationService) {
+        super();
+    }
+
+    loadTable() {
+        this.loadingSubject.next(true);
+        this.policySvc.getPolicyTypes()
+            .pipe(
+                catchError((her: HttpErrorResponse) => {
+                    this.notificationService.error('Failed to get policy types: ' + her.message);
+                    return of([]);
+                }),
+                finalize(() => this.loadingSubject.next(false))
+            )
+            .subscribe((types: PolicyType[]) => {
+                this.rowCount = types.length;
+                this.policyTypeSubject.next(types);
+            });
+    }
+
+    connect(collectionViewer: CollectionViewer): Observable<PolicyType[]> {
+        const dataMutations = [
+            this.policyTypeSubject.asObservable(),
+            this.sort.sortChange
+        ];
+        return merge(...dataMutations).pipe(map(() => {
+            return this.getSortedData([...this.policyTypeSubject.getValue()]);
+        }));
+    }
+
+    disconnect(collectionViewer: CollectionViewer): void {
+        this.policyTypeSubject.complete();
+        this.loadingSubject.complete();
+    }
+
+    private getSortedData(data: PolicyType[]) {
+        if (!this.sort.active || this.sort.direction === '') {
+            return data;
+        }
+
+        return data.sort((a, b) => {
+            const isAsc = this.sort.direction === 'asc';
+            switch (this.sort.active) {
+                case 'name': return compare(a.name, b.name, isAsc);
+                default: return 0;
+            }
+        });
+    }
+}
+
+function compare(a: any, b: any, isAsc: boolean) {
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.html b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.html
new file mode 100644 (file)
index 0000000..0642baa
--- /dev/null
@@ -0,0 +1,54 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+<div mat-dialog-title>
+    Setup RAN Connection
+</div>
+<form [formGroup]="ranDialogForm" novalidate autocomplete="off" (ngSubmit)="setupConnection(ranDialogForm.value)">
+  <div mat-dialog-content>
+    <div name="rantype">
+      <label id="request-type-radio-group-label">RAN type:</label>
+      <mat-radio-group aria-label="RAN Type" formControlName="ranType">
+        <mat-radio-button class="ran-type-radio-button" value="endc">EN-DC</mat-radio-button>
+        <mat-radio-button class="ran-type-radio-button" value="x2">X2</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="RAN Name" formControlName="ranName">
+      <mat-hint align="end">Example: ABCD123456</mat-hint>
+      <mat-error *ngIf="validateControl('ranName') && hasError('ranName', 'required')">Name is required</mat-error>
+      <mat-error *ngIf="hasError('ranName', 'length')">Valid name is required</mat-error>
+    </mat-form-field>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="IP" formControlName="ranIp">
+      <mat-error *ngIf="validateControl('ranIp') && hasError('ranIp', 'required')">IP is required</mat-error>
+      <mat-error *ngIf="hasError('ranIp', 'pattern')">Valid IP is required</mat-error>
+    </mat-form-field>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="Port" formControlName="ranPort">
+      <mat-error *ngIf="validateControl('ranPort') && hasError('ranPort', 'required')">Port is required</mat-error>
+      <mat-error *ngIf="hasError('ranPort', 'pattern')">Valid port number is required</mat-error>
+    </mat-form-field>
+  </div>
+  <div mat-dialog-actions class="modal-footer justify-content-center">
+    <button class="mat-raised-button" (click)="onCancel()">Cancel</button>
+    <button class="mat-raised-button mat-primary" [disabled]="!ranDialogForm.valid || processing">Connect</button>
+  </div>
+</form>
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.scss b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.scss
new file mode 100644 (file)
index 0000000..484ceb9
--- /dev/null
@@ -0,0 +1,29 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+ /* used to place form fields on separate lines/rows in dialog */
+.input-display-block {
+  display: block;
+}
+
+/* leave a bit of space */
+.ran-type-radio-button {
+  margin-left: 5px;
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts
new file mode 100644 (file)
index 0000000..ad87121
--- /dev/null
@@ -0,0 +1,124 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Component, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialogRef } from '@angular/material/dialog';
+import { Observable } from 'rxjs';
+import { finalize } from 'rxjs/operators';
+import { E2SetupRequest, RanDialogFormData } from '../interfaces/e2-mgr.types';
+import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+@Component({
+  selector: 'rd-ran-control-connect-dialog',
+  templateUrl: './ran-connection-dialog.component.html',
+  styleUrls: ['./ran-connection-dialog.component.scss']
+})
+
+export class RanControlConnectDialogComponent implements OnInit {
+
+  public ranDialogForm: FormGroup;
+  public processing = false;
+
+  constructor(
+    private dialogRef: MatDialogRef<RanControlConnectDialogComponent>,
+    private service: E2ManagerService,
+    private errorService: ErrorDialogService,
+    private loadingDialogService: LoadingDialogService,
+    private notifService: NotificationService) {
+    // opens with empty fields; accepts no data to display
+  }
+
+  ngOnInit() {
+    const namePattern = /^([A-Z0-9])+$/;
+    // tslint:disable-next-line:max-line-length
+    const ipPattern = /((((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$))/;
+    const portPattern = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
+    this.ranDialogForm = new FormGroup({
+      ranType: new FormControl('endc'),
+      ranName: new FormControl('', [Validators.required, Validators.pattern(namePattern)]),
+      ranIp: new FormControl('', [Validators.required, Validators.pattern(ipPattern)]),
+      ranPort: new FormControl('', [Validators.required, Validators.pattern(portPattern)])
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  setupConnection = (ranFormValue: RanDialogFormData) => {
+    if (!this.ranDialogForm.valid) {
+      // should never happen
+      return;
+    }
+    this.processing = true;
+    const setupRequest: E2SetupRequest = {
+      ranName: ranFormValue.ranName.trim(),
+      ranIp: ranFormValue.ranIp.trim(),
+      ranPort: ranFormValue.ranPort.trim()
+    };
+    this.loadingDialogService.startLoading('Setting up connection');
+    let observable: Observable<HttpResponse<Object>>;
+    if (ranFormValue.ranType === 'endc') {
+      observable = this.service.endcSetup(setupRequest);
+    } else {
+      observable = this.service.x2Setup(setupRequest);
+    }
+    observable
+      .pipe(
+        finalize(() => this.loadingDialogService.stopLoading())
+      )
+      .subscribe(
+        (response: any) => {
+          this.processing = false;
+          this.notifService.success('Connect request sent!');
+          this.dialogRef.close(true);
+        },
+        ((her: HttpErrorResponse) => {
+          this.processing = false;
+          // the error field carries the server's response
+          let msg = her.message;
+          if (her.error && her.error.message) {
+            msg = her.error.message;
+          }
+          this.errorService.displayError('Connect request failed: ' + msg);
+          // keep the dialog open
+        })
+      );
+  }
+
+  hasError(controlName: string, errorName: string) {
+    if (this.ranDialogForm.controls[controlName].hasError(errorName)) {
+      return true;
+    }
+    return false;
+  }
+
+  validateControl(controlName: string) {
+    if (this.ranDialogForm.controls[controlName].invalid && this.ranDialogForm.controls[controlName].touched) {
+      return true;
+    }
+    return false;
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.html b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.html
new file mode 100644 (file)
index 0000000..01dd292
--- /dev/null
@@ -0,0 +1,89 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+-->
+
+<div class="ran-control__section">
+  <h3 class="rd-global-page-title">RAN Connections</h3>
+
+  <button mat-raised-button (click)="setupRANConnection()">Setup Connection..</button>
+  <button mat-raised-button color="warn" class="disconnect-all-button"
+    (click)="disconnectAllRANConnections()">Disconnect All</button>
+
+  <table mat-table class="ran-control-table mat-elevation-z8" [dataSource]="dataSource">
+
+    <ng-template #noValue></ng-template>
+
+    <ng-container matColumnDef="nbId">
+      <mat-header-cell *matHeaderCellDef>Nodeb ID</mat-header-cell>
+      <mat-cell *matCellDef="let ran">
+        <div *ngIf="ran.nodebIdentity.globalNbId; else noValue">{{ran.nodebIdentity.globalNbId.nbId}}</div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="nodeType">
+      <mat-header-cell *matHeaderCellDef>Node Type</mat-header-cell>
+      <mat-cell *matCellDef="let ran">
+        <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.nodeType}}</div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="ranName">
+      <mat-header-cell *matHeaderCellDef>RAN Name</mat-header-cell>
+      <mat-cell *matCellDef="let ran">
+        <div *ngIf="ran.nodebIdentity; else noValue">{{ran.nodebIdentity.inventoryName}}</div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="ranIp">
+      <mat-header-cell *matHeaderCellDef>IP</mat-header-cell>
+      <mat-cell *matCellDef="let ran">
+        <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.ip}}</div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="ranPort">
+      <mat-header-cell *matHeaderCellDef>Port</mat-header-cell>
+      <mat-cell *matCellDef="let ran">
+        <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.port}}</div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="connectionStatus">
+      <mat-header-cell *matHeaderCellDef>Connection Status</mat-header-cell>
+      <mat-cell *matCellDef="let ran">
+        <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.connectionStatus}}</div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="noRecordsFound">
+      <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+    </ng-container>
+
+    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
+    <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
+    <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': dataSource.rowCount > 0}">
+    </mat-footer-row>
+
+  </table>
+
+  <div class="spinner-container" *ngIf="dataSource.loading$ | async">
+    <mat-spinner diameter=50></mat-spinner>
+  </div>
+
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.scss b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.scss
new file mode 100644 (file)
index 0000000..c86d062
--- /dev/null
@@ -0,0 +1,51 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+ .ran-control__section {
+}
+
+.disconnect-all-button {
+    float: right;
+}
+
+.ran-control-table {
+    width: 100%;
+    min-height: 100px;
+    margin-top: 10px;
+    background-color:transparent;
+}
+
+.spinner-container {
+    height: 100px;
+    width: 100px;
+}
+
+.spinner-container mat-spinner {
+    margin: 0 auto 0 auto;
+}
+
+.version__text {
+    color: gray;
+    letter-spacing: 0.1rem;
+    font-size: 10px;
+}
+
+.display-none {
+    display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.spec.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.spec.ts
new file mode 100644 (file)
index 0000000..aa0e8b8
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RanControlComponent } from './ran-control.component';
+
+describe('RanControlComponent', () => {
+  let component: RanControlComponent;
+  let fixture: ComponentFixture<RanControlComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ RanControlComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(RanControlComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.ts
new file mode 100644 (file)
index 0000000..b5aba66
--- /dev/null
@@ -0,0 +1,107 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { HttpErrorResponse } from '@angular/common/http';
+import { Component, OnInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { finalize } from 'rxjs/operators';
+import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { RanControlConnectDialogComponent } from './ran-connection-dialog.component';
+import { RANControlDataSource } from './ran-control.datasource';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+  selector: 'rd-ran-control',
+  templateUrl: './ran-control.component.html',
+  styleUrls: ['./ran-control.component.scss']
+})
+export class RanControlComponent implements OnInit {
+
+  darkMode: boolean;
+  panelClass: string = "";
+  displayedColumns: string[] = ['nbId', 'nodeType', 'ranName', 'ranIp', 'ranPort', 'connectionStatus'];
+  dataSource: RANControlDataSource;
+
+  constructor(private e2MgrSvc: E2ManagerService,
+    private errorDialogService: ErrorDialogService,
+    private confirmDialogService: ConfirmDialogService,
+    private notificationService: NotificationService,
+    private loadingDialogService: LoadingDialogService,
+    public dialog: MatDialog,
+    public ui: UiService) { }
+
+  ngOnInit() {
+    this.dataSource = new RANControlDataSource(this.e2MgrSvc, this.notificationService);
+    this.dataSource.loadTable();
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+  }
+
+  setupRANConnection() {
+    if (this.darkMode) {
+      this.panelClass = "dark-theme";
+    } else {
+      this.panelClass = "";
+    }
+    const dialogRef = this.dialog.open(RanControlConnectDialogComponent, {
+      panelClass: this.panelClass,
+      width: '450px'
+    });
+    dialogRef.afterClosed()
+      .subscribe((result: boolean) => {
+      if (result) {
+        this.dataSource.loadTable();
+      }
+    });
+  }
+
+  disconnectAllRANConnections() {
+    const aboutError = 'Disconnect all RAN Connections Failed: ';
+    this.confirmDialogService.openConfirmDialog('Are you sure you want to disconnect all RAN connections?')
+      .afterClosed().subscribe( (res: boolean) => {
+        if (res) {
+          this.loadingDialogService.startLoading("Disconnecting");
+          this.e2MgrSvc.nodebPut()
+            .pipe(
+              finalize(() => this.loadingDialogService.stopLoading())
+            )
+            .subscribe(
+            ( body: any ) => {
+              this.notificationService.success('Disconnect succeeded!');
+              this.dataSource.loadTable();
+            },
+            (her: HttpErrorResponse) => {
+              // the error field should have an ErrorTransport object
+              let msg = her.message;
+              if (her.error && her.error.message) {
+                msg = her.error.message;
+              }
+              this.errorDialogService.displayError('Disconnect failed: ' + msg);
+            }
+          );
+        }
+      });
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.datasource.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-control.datasource.ts
new file mode 100644 (file)
index 0000000..b23a3cd
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize } from 'rxjs/operators';
+import { E2RanDetails } from '../interfaces/e2-mgr.types';
+import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class RANControlDataSource extends DataSource<E2RanDetails> {
+
+  private ranControlSubject = new BehaviorSubject<E2RanDetails[]>([]);
+
+  private loadingSubject = new BehaviorSubject<boolean>(false);
+
+  public loading$ = this.loadingSubject.asObservable();
+
+  public rowCount = 1; // hide footer during intial load
+
+  constructor(private e2MgrSvcservice: E2ManagerService,
+    private notificationService: NotificationService) {
+    super();
+  }
+
+  loadTable() {
+    this.loadingSubject.next(true);
+    this.e2MgrSvcservice.getRan()
+      .pipe(
+        catchError( (her: HttpErrorResponse) => {
+          console.log('RANControlDataSource failed: ' + her.message);
+          this.notificationService.error('Failed to get RAN details: ' + her.message);
+          return of([]);
+        }),
+        finalize( () =>  this.loadingSubject.next(false) )
+      )
+      .subscribe( (ranControl: E2RanDetails[] ) => {
+        this.rowCount = ranControl.length;
+        this.ranControlSubject.next(ranControl);
+      });
+  }
+
+  connect(collectionViewer: CollectionViewer): Observable<E2RanDetails[]> {
+    return this.ranControlSubject.asObservable();
+  }
+
+  disconnect(collectionViewer: CollectionViewer): void {
+    this.ranControlSubject.complete();
+    this.loadingSubject.complete();
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/rd-routing.module.ts b/dashboard/webapp-frontend/src/app/rd-routing.module.ts
new file mode 100644 (file)
index 0000000..520f9a5
--- /dev/null
@@ -0,0 +1,43 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Routes, RouterModule } from '@angular/router';
+import { MainComponent } from './main/main.component';
+import { PolicyControlComponent} from './policy-control/policy-control.component';
+
+
+const routes: Routes = [
+    {path: '', component: MainComponent},  
+    {path: 'policy', component: PolicyControlComponent}
+];
+
+@NgModule({
+  imports: [
+      CommonModule,
+      RouterModule.forRoot(routes)],
+  exports: [
+      RouterModule
+    ],
+    declarations: []
+})
+
+export class RdRoutingModule { }
diff --git a/dashboard/webapp-frontend/src/app/rd.component.html b/dashboard/webapp-frontend/src/app/rd.component.html
new file mode 100644 (file)
index 0000000..aef2941
--- /dev/null
@@ -0,0 +1,126 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<!-- Slide Menu-->
+<mat-drawer-container autosize >
+  <mat-drawer #drawer mode="push" [ngClass]="{'side-menu__dark': darkModeActive}">
+  <section class="menu-header" [ngClass]="{'menu-header__dark': darkModeActive}">
+
+    <div class="left__section3Col" *ngIf="drawer.opened"  [ngClass]="{'menumargin-top': drawer.opened}">
+      <svg (click)="drawer.toggle()" class="hamburger__icon" id="Menu_Burger_Icon" data-name="Menu Burger Icon"
+           viewBox="31.5 30 49.9 32">
+        <rect id="Rectangle_9" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+              class="hamburger__icon__fill" data-name="Rectangle 9" rx="2"
+              transform="translate(31.5 58)"/>
+        <rect id="Rectangle_10" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+              class="hamburger__icon__fill" data-name="Rectangle 10" rx="2"
+              transform="translate(31.5 44)"/>
+        <rect id="Rectangle_11" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+              class="hamburger__icon__fill" data-name="Rectangle 11" rx="2"
+              transform="translate(31.5 30)"/>
+      </svg>
+      <img src="../../assets/oran-logo.png" width="180%" height="100%" style="position: relative; z-index: 50"/>
+      <svg class="logo__icon" viewBox="150.3 22.2 400 50">
+        <text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85"
+              font-size="30" font-weight="600"
+              letter-spacing=".1em" transform="translate(149 56)">
+          <tspan x="0" y="0">Non RT RIC Dashboard</tspan>
+        </text>
+      </svg>
+
+    </div>
+
+      <div class="profile-image__container">
+        <img src="assets/profile_default.png" alt="profile-image"
+             class="profile__image">
+      </div>
+      <div class="account-details">
+        <span class="name__text">Demo</span>
+        <span class="email__text">demo@o-ran-sc.org</span>
+      </div>
+    </section>
+    <section #sidenav class="menu-body" >
+       <rd-sidenav-list ></rd-sidenav-list>
+    </section>
+    <section class="menu-footer">
+
+    </section>
+</mat-drawer>
+
+<mat-drawer-content>
+<div class="root__container">
+
+  <header [ngClass]="{'main__header-dark': darkModeActive}" class="main__header">
+
+    <div class="left__section3Col" *ngIf="!drawer.opened">
+      <svg (click)="drawer.toggle()" class="hamburger__icon" id="Menu_Burger_Icon" data-name="Menu Burger Icon"
+           viewBox="31.5 30 49.9 32">
+        <rect id="Rectangle_9" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+              class="hamburger__icon__fill" data-name="Rectangle 9" rx="2"
+              transform="translate(31.5 58)"/>
+        <rect id="Rectangle_10" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+              class="hamburger__icon__fill" data-name="Rectangle 10" rx="2"
+              transform="translate(31.5 44)"/>
+        <rect id="Rectangle_11" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+              class="hamburger__icon__fill" data-name="Rectangle 11" rx="2"
+              transform="translate(31.5 30)"/>
+      </svg>
+      <img src="../../assets/oran-logo.png" width="180%" height="100%" style="position: relative; z-index: 50"/>
+      <svg class="logo__icon" viewBox="150.3 22.2 400 50">
+        <text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85"
+              font-size="30" font-weight="600"
+              letter-spacing=".1em" transform="translate(149 56)">
+          <tspan x="0" y="0">Non RT RIC Dashboard</tspan>
+        </text>
+      </svg>
+
+    </div>
+
+    <h3 class="date__text"></h3>
+
+    <div class="mode-toggle__container">
+      <span class="mode-toggle__text">Light</span>
+      <label class="toggle-button__container">
+        <input (click)="modeToggleSwitch()" type="checkbox" class="mode-toggle__input"/>
+        <span [ngClass]="{'mode-toggle__bg-checked': darkModeActive}" class="mode-toggle__bg"></span>
+        <span [ngClass]="{'mode-toggle__circle-checked': darkModeActive}" class="mode-toggle__circle"></span>
+      </label>
+      <span class="mode-toggle__text">Dark</span>
+    </div>
+
+  </header>
+
+  <!-- Main Content -->
+  <main class="main__container" [ngClass]="{'main-container__bg-dark': darkModeActive}" >
+    <div class = main__container__body [ngClass]="{'dark-theme': darkModeActive}">
+      <div class="main-container__bg"></div>
+      <router-outlet></router-outlet>
+    </div>
+  </main>
+
+  <!-- Footer -->
+  <footer class="main__footer" [ngClass]="{'main-footer__bg-dark': darkModeActive}">
+    <div class="main-footer__bg">
+      <rd-footer></rd-footer>
+    </div>
+  </footer>
+
+</div>
+</mat-drawer-content>
+</mat-drawer-container>
diff --git a/dashboard/webapp-frontend/src/app/rd.component.scss b/dashboard/webapp-frontend/src/app/rd.component.scss
new file mode 100644 (file)
index 0000000..f9e4735
--- /dev/null
@@ -0,0 +1,376 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+.root__container {
+  width: 100vw;
+  height: 100vh;
+  display: grid;
+  grid-template-columns: auto;
+  grid-template-rows: 0.5fr auto;
+  position: relative;
+}
+
+/*
+================
+    Header
+================
+*/
+mat-sidenav-container, mat-sidenav-content, mat-sidenav {
+    height: 100%;
+}
+
+mat-sidenav {
+    width: 250px;
+}
+
+main {
+    padding: 10px;
+}
+
+/*
+    Slide Menu
+= = = = = = = = =
+*/
+.side-menu__dark {
+  color: white;
+  background: gray;
+}
+
+.side-menu__container {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  pointer-events: none;
+  z-index: 25;
+}
+
+.side-menu__container-active {
+  pointer-events: auto;
+}
+
+.side-menu__container::before {
+  content: '';
+  cursor: pointer;
+  position: absolute;
+  display: block;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+  background-color: #0c1066;
+  opacity: 0;
+  transition: opacity 300ms linear;
+  will-change: opacity;
+}
+
+.side-menu__container-active::before {
+  opacity: 0.3;
+}
+
+.slide-menu {
+  box-sizing: border-box;
+  transform: translateX(-103%);
+  position: relative;
+  top: 0;
+  left: 0;
+  z-index: 10;
+  height: 100%;
+  width: 90%;
+  max-width: 26rem;
+  background-color: white;
+  box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+  display: grid;
+  grid-template-columns: 1fr;
+  grid-template-rows: 2fr 4fr 1fr;
+  grid-gap: 1rem;
+  transition: transform 300ms linear;
+  will-change: transform;
+}
+
+.slide-menu-active {
+  transform: none;
+}
+.menu-header.menu-header__dark {
+  background: #2B244D;
+}
+
+.menu-header {
+  background: linear-gradient(to right, rgb(181, 199, 192), #82bbb6);
+  display: grid;
+  grid-template-rows: 1fr 4fr;
+  grid-template-columns: 1fr 4fr;
+  grid-template-areas: "greeting greeting" "image details";
+  box-sizing: border-box;
+  width: 100%;
+  align-content: center;
+  color: white;
+  box-shadow: 0 0.5rem 2rem rgba(0, 0, 255, 0.2);
+}
+
+mat-drawer {
+  width: 340px;
+}
+
+mat-drawer-content {
+  overflow: overlay;
+}
+
+.menumargin-top {
+    margin-top: 10px;
+}
+
+.greeting__text {
+  grid-area: greeting;
+  font-size: 1.25rem;
+  letter-spacing: 0.15rem;
+  text-transform: uppercase;
+  margin-top: 1rem;
+  justify-self: center;
+  align-self: center;
+}
+
+.account-details {
+  grid-area: details;
+  display: flex;
+  flex-flow: column;
+  margin-left: 1rem;
+  align-self: center;
+}
+
+.name__text {
+  font-size: 1.15rem;
+  margin-bottom: 0.5rem;
+}
+
+.email__text {
+  font-size: 0.9rem;
+  letter-spacing: 0.1rem;
+}
+
+.menu-body {
+  display: grid;
+  width: 100%;
+}
+
+.profile-image__container {
+  grid-area: image;
+  margin-right: 0.5rem;
+  border-radius: 50%;
+  height: 4rem;
+  width: 4rem;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: white;
+  align-self: center;
+  margin-left: 2rem;
+}
+
+.profile__image {
+  max-width: 4rem;
+}
+
+.home_bg_image{
+    height:40em;
+    background-size:cover;
+    width:auto;
+    background-image:url('../assets/intelligence.png');
+    background-position:50% 50%;
+}
+
+/*Header*/
+.main__header {
+  width: 100%;
+  display: grid;
+  grid-template-columns: 1fr 1fr 0.25fr;
+  grid-template-rows: 1fr;
+  box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+  height: 4rem;
+  margin: 0;
+  align-items: center;
+  transition: background-color 500ms linear;
+  animation: fadein 1s ease-in-out 0ms 1;
+}
+
+.main__header-dark {
+  background-color: #2B244D;
+  color: white;
+}
+
+.toggle-button__container {
+  cursor: pointer;
+  position: relative;
+  margin: 0 0.5rem;
+}
+
+.mode-toggle__input {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+}
+
+.mode-toggle__bg {
+  height: 1rem;
+  width: 2rem;
+  border-radius: 0.5rem;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: inline-block;
+  transition: background-color 300ms linear;
+}
+
+.mode-toggle__circle {
+  height: 1.30rem;
+  width: 1.30rem;
+  background-color: #2B244D;
+  position: absolute;
+  top: -0.2rem;
+  border-radius: 50%;
+  box-shadow: 0 0 0 rgba(0, 0, 255, 0.5);
+  transition: left 300ms linear;
+  left: 0.1rem;
+}
+
+.mode-toggle__circle-checked {
+  background-color: white;
+  left: 1.75rem;
+}
+
+.mode-toggle__bg-checked {
+  background-color: #FF0070;
+}
+
+.mode-toggle__text {
+  font-size: 0.75rem;
+  text-transform: uppercase;
+  letter-spacing: 0.1rem;
+}
+
+/*Content*/
+.left__section {
+  display: grid;
+  grid-template-rows: 1fr;
+  grid-template-columns: 1fr 1fr;
+  max-width: 5rem;
+}
+
+.left__section3Col {
+  display: grid;
+  grid-template-rows: 1fr;
+  grid-template-columns: 1fr 1fr 1fr;
+  max-width: 6rem;
+}
+
+.date__text {
+  text-transform: uppercase;
+  letter-spacing: 0.1rem;
+  display: inline;
+  margin: 0.5rem 0;
+}
+
+/*SVGs*/
+.hamburger__icon {
+  position: relative;
+  z-index: 35;
+  height: 2rem;
+  padding: 0.5rem 1.5rem;
+  margin-right: 1rem;
+  cursor: pointer;
+}
+
+.logo__icon {
+  height: 2rem;
+  margin-left: 1rem;
+}
+
+.logo__text {
+  fill: #2B244D;
+}
+
+.logo__text-dark {
+  fill: #ffffff;
+}
+
+.hamburger__icon__fill {
+  fill: #2B244D;
+}
+
+.hamburger__icon__fill-dark {
+  fill: #ffffff;
+}
+
+/*
+================
+    Body
+================
+*/
+
+.main__container {
+  height: 100%;
+  width: 100%;
+}
+
+.main__container__body {
+  min-height: calc(100vh - 140px);
+}
+
+.main-container__bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: -2;
+  opacity: 0;
+  background: white;
+  transition: opacity 300ms linear;
+}
+
+.main-container__bg-dark {
+  opacity: 1;
+  background: linear-gradient(to bottom, #B290FF, #2E1D65);
+  transition: opacity 300ms linear;
+}
+
+/*
+================-
+    Footer
+================
+*/
+.main__footer {
+  background: transparent;
+  z-index: 100;
+}
+
+.main__footer-dark {
+  background-color: #2B244D;
+  color: white;
+}
+
+.main-footer__bg-dark {
+  opacity: 1;
+  background: linear-gradient(to bottom, #B290FF, #2E1D65);
+  transition: opacity 300ms linear;
+}
+
+@media only screen and (max-width: 300px) {
+  .slide-menu {
+    width: 100%;
+  }
+}
diff --git a/dashboard/webapp-frontend/src/app/rd.component.spec.ts b/dashboard/webapp-frontend/src/app/rd.component.spec.ts
new file mode 100644 (file)
index 0000000..852386c
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed, async } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [
+        RouterTestingModule
+      ],
+      declarations: [
+        AppComponent
+      ],
+    }).compileComponents();
+  }));
+
+  it('should create the app', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  });
+
+  it(`should have as title 'dashApp'`, () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('dashApp');
+  });
+
+  it('should render title in a h1 tag', () => {
+    const fixture = TestBed.createComponent(AppComponent);
+    fixture.detectChanges();
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('h1').textContent).toContain('Welcome to dashApp!');
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/rd.component.ts b/dashboard/webapp-frontend/src/app/rd.component.ts
new file mode 100644 (file)
index 0000000..1673280
--- /dev/null
@@ -0,0 +1,49 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+import { UiService } from './services/ui/ui.service';
+
+@Component({
+  selector: 'rd-root',
+  templateUrl: './rd.component.html',
+  styleUrls: ['./rd.component.scss']
+})
+export class RdComponent implements OnInit {
+  showMenu = false;
+  darkModeActive: boolean;
+
+  constructor(public ui: UiService) {
+  }
+
+  ngOnInit() {
+    this.ui.darkModeState.subscribe((value) => {
+      this.darkModeActive = value;
+    });
+  }
+
+  toggleMenu() {
+    this.showMenu = !this.showMenu;
+  }
+
+  modeToggleSwitch() {
+    this.ui.darkModeState.next(!this.darkModeActive);
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/rd.module.ts b/dashboard/webapp-frontend/src/app/rd.module.ts
new file mode 100644 (file)
index 0000000..378374a
--- /dev/null
@@ -0,0 +1,164 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { BrowserModule } from '@angular/platform-browser';
+// tslint:disable-next-line:max-line-length
+import {MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule,
+    MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule,
+    MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatPaginatorModule,
+    MatProgressSpinnerModule, MatSelectModule, MatSidenavModule, MatSliderModule,
+    MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatTableModule,
+    MatTabsModule,  MatToolbarModule} from '@angular/material';
+import { BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
+import { MatRadioModule } from '@angular/material/radio';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { ChartsModule } from 'ng2-charts';
+import { MDBBootstrapModule } from 'angular-bootstrap-md';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ToastrModule } from 'ngx-toastr';
+
+import { AddDashboardUserDialogComponent } from './user/add-dashboard-user-dialog/add-dashboard-user-dialog.component';
+import { AppControlComponent } from './app-control/app-control.component';
+import { AppMgrService } from './services/app-mgr/app-mgr.service';
+import { ConfirmDialogComponent } from './ui/confirm-dialog/confirm-dialog.component';
+import { ControlComponent } from './control/control.component';
+import { DashboardService } from './services/dashboard/dashboard.service';
+import { E2ManagerService } from './services/e2-mgr/e2-mgr.service';
+import { EditDashboardUserDialogComponent } from './user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component';
+import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
+import { ErrorDialogService } from './services/ui/error-dialog.service';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { FooterComponent } from './footer/footer.component';
+import { LoadingDialogComponent } from './ui/loading-dialog/loading-dialog.component';
+import { MainComponent } from './main/main.component';
+import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
+import { ModalEventComponent } from './ui/modal-event/modal-event.component';
+import { PolicyCardComponent } from './ui/policy-card/policy-card.component';
+import { PolicyControlComponent } from './policy-control/policy-control.component';
+import { PolicyInstanceComponent } from './policy-control/policy-instance.component';
+import { PolicyInstanceDialogComponent } from './policy-control/policy-instance-dialog.component';
+import { RanControlComponent } from './ran-control/ran-control.component';
+import { RanControlConnectDialogComponent } from './ran-control/ran-connection-dialog.component';
+import { RdComponent } from './rd.component';
+import { RdRoutingModule } from './rd-routing.module';
+import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component';
+import { UiService } from './services/ui/ui.service';
+import { UserComponent } from './user/user.component';
+
+@NgModule({
+  declarations: [
+    AddDashboardUserDialogComponent,
+    AppControlComponent,
+    ConfirmDialogComponent,
+    ControlComponent,
+    EditDashboardUserDialogComponent,
+    ErrorDialogComponent,
+    FooterComponent,
+    LoadingDialogComponent,
+    MainComponent,
+    ModalEventComponent,
+    PolicyCardComponent,
+    PolicyControlComponent,
+    PolicyInstanceComponent,
+    PolicyInstanceDialogComponent,
+    RanControlComponent,
+    RanControlConnectDialogComponent,
+    RdComponent,
+    SidenavListComponent,   
+    UserComponent
+  ],
+  imports: [
+    BrowserModule,
+    BrowserAnimationsModule,
+    ChartsModule,
+    FlexLayoutModule,
+    FormsModule,
+    HttpClientModule,
+    MatButtonModule,
+    MatButtonToggleModule,
+    MatCardModule,
+    MatCheckboxModule,
+    MatDialogModule,
+    MaterialDesignFrameworkModule,
+    MatExpansionModule,
+    MatFormFieldModule,
+    MatGridListModule,
+    MatIconModule,
+    MatInputModule,
+    MatListModule,
+    MatMenuModule,
+    MatPaginatorModule,
+    MatProgressSpinnerModule,
+    MatRadioModule,
+    MatSelectModule,
+    MatSliderModule,
+    MatSidenavModule,
+    MatSlideToggleModule,
+    MatSnackBarModule,
+    MatSortModule,
+    MatTableModule,
+    MatTabsModule,
+    MatToolbarModule,
+    MatTooltipModule,
+    MDBBootstrapModule.forRoot(),
+    RdRoutingModule,
+    ReactiveFormsModule,
+    ToastrModule.forRoot()
+  ],
+  exports: [
+    ErrorDialogComponent,
+    FormsModule,
+    MatButtonModule,
+    MatButtonToggleModule,
+    MatCardModule,
+    MatDialogModule,
+    MatExpansionModule,
+    MatFormFieldModule,
+    MatGridListModule,
+    MatIconModule,
+    MatInputModule,
+    MatListModule,
+    MatSidenavModule,
+    MatSliderModule,
+    MatSlideToggleModule,
+    MatTabsModule,
+    RanControlConnectDialogComponent
+  ],
+  entryComponents: [
+    AddDashboardUserDialogComponent,
+    ConfirmDialogComponent,
+    EditDashboardUserDialogComponent,
+    ErrorDialogComponent,
+    LoadingDialogComponent,
+    PolicyInstanceDialogComponent,
+    RanControlConnectDialogComponent
+  ],
+  providers: [
+    AppMgrService,
+    DashboardService,
+    E2ManagerService,
+    ErrorDialogService,
+    UiService
+  ],
+  bootstrap: [RdComponent]
+})
+export class RdModule { }
diff --git a/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.spec.ts
new file mode 100644 (file)
index 0000000..b3bf4d4
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { ACXappService } from './ac-xapp.service';
+
+describe('ACXappService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: ACXappService = TestBed.get(ACXappService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.ts b/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.ts
new file mode 100644 (file)
index 0000000..d25e9db
--- /dev/null
@@ -0,0 +1,83 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+ import { Injectable } from '@angular/core';
+ import { HttpClient } from '@angular/common/http';
+ import { Observable } from 'rxjs';
+ import { map } from 'rxjs/operators';
+import { ACAdmissionIntervalControl, ACAdmissionIntervalControlAck } from '../../interfaces/ac-xapp.types';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+
+/**
+ * Services for calling the Dashboard's A1 endpoints to get/put AC policies.
+ */
+@Injectable({
+  providedIn: 'root'
+})
+export class ACXappService {
+
+  private basePath = 'api/a1-p';
+  private policyPath = 'policies';
+  private acPolicyName = 'admission_control_policy';
+
+  private buildPath(...args: any[]) {
+    let result = this.basePath;
+    args.forEach(part => {
+      result = result + '/' + part;
+    });
+    return result;
+  }
+
+  constructor(private httpClient: HttpClient) {
+    // injects to variable httpClient
+  }
+
+  /**
+   * Gets version details
+   * @returns Observable that should yield a String
+   */
+  getVersion(): Observable<string> {
+    const url = this.buildPath('version');
+    return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+      // Extract the string here
+      map(res => res['data'])
+    );
+  }
+
+  /**
+   * Gets admission control policy.
+   * @returns Observable that should yield an ACAdmissionIntervalControl
+   */
+  getPolicy(): Observable<ACAdmissionIntervalControl> {
+    const url = this.buildPath(this.policyPath, this.acPolicyName);
+    return this.httpClient.get<ACAdmissionIntervalControl>(url);
+  }
+
+  /**
+   * Puts admission control policy.
+   * @param policy an instance of ACAdmissionIntervalControl
+   * @returns Observable that should yield a response code, no data
+   */
+  putPolicy(policy: ACAdmissionIntervalControl): Observable<any> {
+    const url = this.buildPath(this.policyPath, this.acPolicyName);
+    return this.httpClient.put<ACAdmissionIntervalControlAck>(url, policy, { observe: 'response' });
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.spec.ts b/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.spec.ts
new file mode 100644 (file)
index 0000000..c47dcd8
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+ import { TestBed } from '@angular/core/testing';
+
+import { ANRXappService } from './anr-xapp.service';
+
+describe('ANRXappService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: ANRXappService = TestBed.get(ANRXappService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.ts b/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.ts
new file mode 100644 (file)
index 0000000..e06b708
--- /dev/null
@@ -0,0 +1,136 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+import { ANRNeighborCellRelation, ANRNeighborCellRelationMod } from '../../interfaces/anr-xapp.types';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ANRXappService {
+
+  // Trailing slashes are confusing so omit them here
+  private basePath = 'api/xapp/anr';
+  private ncrtPath = 'ncrt';
+  private servingPath = 'servingcells';
+  private neighborPath = 'neighborcells';
+
+  constructor(private httpClient: HttpClient) {
+    // injects to variable httpClient
+  }
+
+  private buildPath(...args: any[]) {
+    let result = this.basePath;
+    args.forEach(part => {
+      result = result + '/' + part;
+    });
+    return result;
+  }
+
+  /**
+   * Gets ANR xApp client version details
+   * @returns Observable that should yield a String
+   */
+  getVersion(): Observable<string> {
+    const url = this.buildPath('version');
+    return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+      // Extract the string here
+      map(res => res['data'])
+    );
+  }
+
+  /**
+   * Performs a liveness probe
+   * @returns Observable that should yield a response code (no data)
+   */
+  getHealthAlive(): Observable<any> {
+    const url = this.buildPath('health/alive');
+    return this.httpClient.get(url, { observe: 'response' });
+  }
+
+  /**
+   * Performs a readiness probe
+   * @returns Observable that should yield a response code (no data)
+   */
+  getHealthReady(): Observable<any> {
+    const url = this.buildPath('health/ready');
+    return this.httpClient.get(url, { observe: 'response' });
+  }
+
+/**
+ * Gets array of gNodeB IDs
+ * @returns Observable that should yield a string array
+ */
+  getgNodeBs(): Observable<string[]> {
+    const url = this.buildPath('gnodebs');
+    return this.httpClient.get<string[]>(url).pipe(
+      // Extract the array of IDs here
+      map(res => res['gNodeBIds'])
+    );
+  }
+
+  /**
+   * Gets the neighbor cell relation table for all gNodeBs or based on query parameters
+   * @param ggnbId Optional parameter for the gNB ID
+   * @param servingCellNrcgi Serving cell NRCGI
+   * @param neighborCellNrpci Neighbor cell NRPCI
+   * @returns Observable of ANR neighbor cell relation array
+   */
+  getNcrtInfo(ggnodeb: string = '', servingCellNrcgi: string = '', neighborCellNrpci: string = ''): Observable<ANRNeighborCellRelation[]> {
+    const url = this.buildPath(this.ncrtPath);
+    return this.httpClient.get<ANRNeighborCellRelation[]>(url, {
+      params: new HttpParams()
+        .set('ggnodeb', ggnodeb)
+        .set('servingCellNrcgi', servingCellNrcgi)
+        .set('neighborCellNrpci', neighborCellNrpci)
+    }).pipe(
+      // Extract the array of relations here
+      map(res => res['ncrtRelations'])
+    );
+  }
+
+  /**
+   * Modify neighbor cell relation based on Serving Cell NRCGI and Neighbor Cell NRPCI
+   * @param servingCellNrcgi Serving cell NRCGI
+   * @param neighborCellNrpci Neighbor cell NRPCI
+   * @param mod Values to store in the specified relation
+   * @returns Observable that yields a response code only, no data
+   */
+  modifyNcr(servingCellNrcgi: string, neighborCellNrpci: string, mod: ANRNeighborCellRelationMod): Observable<Object> {
+    const url = this.buildPath(this.ncrtPath, this.servingPath, servingCellNrcgi, this.neighborPath, neighborCellNrpci);
+    return this.httpClient.put(url, mod, { observe: 'response' });
+  }
+
+  /**
+   * Deletes neighbor cell relation based on Serving Cell NRCGI and Neighbor Cell NRPCI
+   * @param servingCellNrcgi Serving cell NRCGI
+   * @param neighborCellNrpci Neighbor cell NRPCI
+   * @returns Observable that yields a response code only, no data
+   */
+  deleteNcr(servingCellNrcgi: string, neighborCellNrpci: string): Observable<Object> {
+    const url = this.buildPath(this.ncrtPath, this.servingPath, servingCellNrcgi, this.neighborPath, neighborCellNrpci);
+    return this.httpClient.delete(url, { observe: 'response' });
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.spec.ts b/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.spec.ts
new file mode 100644 (file)
index 0000000..fcc2aaa
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { AppMgrService } from './app-mgr.service';
+
+describe('AppMgrService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: AppMgrService = TestBed.get(AppMgrService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.ts b/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.ts
new file mode 100644 (file)
index 0000000..afe7a65
--- /dev/null
@@ -0,0 +1,61 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { XMXappInfo, XMDeployableApp, XMDeployedApp } from '../../interfaces/app-mgr.types';
+
+@Injectable()
+export class AppMgrService {
+
+  constructor(private httpClient: HttpClient) {
+    // injects to variable httpClient
+  }
+
+  private basePath = 'api/appmgr';
+
+  getDeployable(): Observable<XMDeployableApp[]> {
+    return this.httpClient.get<XMDeployableApp[]>(this.basePath + '/xapps/list');
+  }
+
+  getDeployed(): Observable<XMDeployedApp[]> {
+    return this.httpClient.get<XMDeployedApp[]>(this.basePath + '/xapps');
+  }
+
+  deployXapp(name: string): Observable<HttpResponse<Object>> {
+    const xappInfo: XMXappInfo = { name: name };
+    return this.httpClient.post((this.basePath + '/xapps'), xappInfo, { observe: 'response' });
+  }
+
+  undeployXapp(name: string): Observable<HttpResponse<Object>> {
+    return this.httpClient.delete((this.basePath + '/xapps'+ '/' + name), { observe: 'response' });
+  }
+
+  getConfig(): Observable<any[]>{
+    return this.httpClient.get<any[]>("/assets/mockdata/config.json");
+    //return this.httpClient.get<any[]>((this.basePath  + '/config'));
+  }
+
+  putConfig(config: any): Observable<HttpResponse<Object>> {
+    return this.httpClient.put((this.basePath + '/config' ), config, { observe: 'response' });
+  }
+
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts b/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts
new file mode 100644 (file)
index 0000000..9d007ad
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { CaasIngressService } from './caas-ingress.service';
+
+describe('CaasIngressService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: CaasIngressService = TestBed.get(CaasIngressService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts b/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts
new file mode 100644 (file)
index 0000000..359a08f
--- /dev/null
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { V1PodList } from '@kubernetes/client-node';
+import { Observable } from 'rxjs';
+
+/**
+* Services for calling the Dashboard's caas-ingress endpoints to get Kubernetes details.
+*/
+@Injectable({
+  providedIn: 'root'
+})
+export class CaasIngressService {
+
+  private basePath = 'api/caas-ingress';
+  private podsPath = 'pods';
+
+  private buildPath(...args: any[]) {
+    let result = this.basePath;
+    args.forEach(part => {
+      result = result + '/' + part;
+    });
+    return result;
+  }
+
+  constructor(private httpClient: HttpClient) {
+    // injects to variable httpClient
+  }
+
+  /**
+   * Gets list of pods
+   * @returns Observable that should yield a V1PodList
+   */
+  getPodList(cluster: string, namespace: string): Observable<V1PodList> {
+    const url = this.buildPath('pods', 'cluster', cluster, 'namespace', namespace);
+    return this.httpClient.get<V1PodList>(url);
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.spec.ts b/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.spec.ts
new file mode 100644 (file)
index 0000000..606adb3
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { DashboardService } from './dashboard.service';
+
+describe('DashboardService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: DashboardService = TestBed.get(DashboardService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.ts b/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.ts
new file mode 100644 (file)
index 0000000..a9f2df6
--- /dev/null
@@ -0,0 +1,64 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { DashboardSuccessTransport, EcompUser } from '../../interfaces/dashboard.types';
+
+@Injectable({
+  providedIn: 'root'
+})
+
+/**
+ * Services to query the dashboard's admin endpoints.
+ */
+export class DashboardService {
+
+  private basePath = 'api/admin/';
+
+  constructor(private httpClient: HttpClient) {
+    // injects to variable httpClient
+  }
+
+ /**
+   * Checks app health
+   * @returns Observable that should yield a DashboardSuccessTransport
+   */
+  getHealth(): Observable<DashboardSuccessTransport> {
+    return this.httpClient.get<DashboardSuccessTransport>(this.basePath + 'health');
+  }
+
+  /**
+   * Gets Dashboard version details
+   * @returns Observable that should yield a DashboardSuccessTransport object
+   */
+  getVersion(): Observable<DashboardSuccessTransport> {
+    return this.httpClient.get<DashboardSuccessTransport>(this.basePath + 'version');
+  }
+
+  /**
+   * Gets Dashboard users
+   * @returns Observable that should yield a EcompUser array
+   */
+  getUsers(): Observable<EcompUser[]> {
+    return this.httpClient.get<EcompUser[]>(this.basePath + 'user');
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.spec.ts b/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.spec.ts
new file mode 100644 (file)
index 0000000..b9e009c
--- /dev/null
@@ -0,0 +1,33 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { E2ManagerService } from './e2-mgr.service';
+
+describe('CatalogService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: E2ManagerService
+   = TestBed.get(E2ManagerService
+    );
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.ts b/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.ts
new file mode 100644 (file)
index 0000000..b3388cf
--- /dev/null
@@ -0,0 +1,83 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { E2RanDetails, E2SetupRequest } from '../../interfaces/e2-mgr.types';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class E2ManagerService {
+
+  private basePath = 'api/e2mgr/nodeb/';
+
+  constructor(private httpClient: HttpClient) {
+    // injects to variable httpClient
+  }
+
+  /**
+   * Gets E2 client version details
+   * @returns Observable that should yield a String
+   */
+  getVersion(): Observable<string> {
+    const url = this.basePath + 'version';
+    return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+      // Extract the string here
+      map(res => res['data'])
+    );
+  }
+
+  /**
+   * Gets RAN details
+   * @returns Observable that should yield an array of objects
+   */
+  getRan(): Observable<Array<E2RanDetails>> {
+    return this.httpClient.get<Array<E2RanDetails>>(this.basePath + 'ran');
+  }
+
+  /**
+   * Sends a request to setup an ENDC/gNodeB connection
+   * @returns Observable. On success there is no data, only a code.
+   */
+  endcSetup(req: E2SetupRequest): Observable<HttpResponse<Object>> {
+    return this.httpClient.post(this.basePath + 'endc-setup', req, { observe: 'response' });
+  }
+
+  /**
+   * Sends a request to setup an X2/eNodeB connection
+   * @returns Observable. On success there is no data, only a code.
+   */
+  x2Setup(req: E2SetupRequest): Observable<HttpResponse<Object>> {
+    return this.httpClient.post(this.basePath + 'x2-setup', req, { observe: 'response' });
+  }
+
+  /**
+   * Sends a request to drop all RAN connections
+   * @returns Observable with body.
+   */
+  nodebPut(): Observable<any> {
+    return this.httpClient.put((this.basePath + 'shutdown'), { observe: 'body' });
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/policy/policy.service.spec.ts b/dashboard/webapp-frontend/src/app/services/policy/policy.service.spec.ts
new file mode 100644 (file)
index 0000000..de1f4e6
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { PolicyService } from './policy.service';
+
+describe('PolicyService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: PolicyService = TestBed.get(PolicyService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/policy/policy.service.ts b/dashboard/webapp-frontend/src/app/services/policy/policy.service.ts
new file mode 100644 (file)
index 0000000..1c87081
--- /dev/null
@@ -0,0 +1,104 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { PolicyType, PolicyInstance, PolicyInstanceAck } from '../../interfaces/policy.types';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+
+/**
+ * Services for calling the policy endpoints.
+ */
+@Injectable({
+    providedIn: 'root'
+})
+export class PolicyService {
+
+    private basePath = 'api/policy';
+    private policyTypePath = 'policytypes';
+    private policyPath = 'policies';
+
+    private buildPath(...args: any[]) {
+        let result = this.basePath;
+        args.forEach(part => {
+            result = result + '/' + part;
+        });
+        return result;
+    }
+
+    constructor(private httpClient: HttpClient) {
+        // injects to variable httpClient
+    }
+
+    /**
+     * Gets version details
+     * @returns Observable that should yield a String
+     */
+    getVersion(): Observable<string> {
+        const url = this.buildPath('version');
+        return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+            // Extract the string here
+            map(res => res['data'])
+        );
+    }
+
+    getPolicyTypes(): Observable<PolicyType[]> {
+        const url = this.buildPath(this.policyTypePath);
+        return this.httpClient.get<PolicyType[]>(url);
+    }
+
+    getPolicyInstances(policyTypeId: number): Observable<PolicyInstance[]> {
+        const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath);
+        return this.httpClient.get<PolicyInstance[]>(url);
+    }
+
+    /**
+     * Gets policy parameters.
+     * @returns Observable that should yield a policy instance
+     */
+    getPolicy(policyTypeId: number, policyInstanceId: string): Observable<any> {
+        const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+        return this.httpClient.get<any>(url);
+    }
+
+    /**
+     * Creates or replaces policy instance.
+     * @param policyTypeId ID of the policy type that the instance will have
+     * @param policyInstanceId ID of the instance
+     * @param policyJson Json with the policy content
+     * @returns Observable that should yield a response code, no data
+     */
+    putPolicy(policyTypeId: number, policyInstanceId: string, policyJson: string): Observable<any> {
+        const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+        return this.httpClient.put<PolicyInstanceAck>(url, policyJson, { observe: 'response' });
+    }
+
+    /**
+     * Deletes a policy instance.
+     * @param policyTypeId
+     * @returns Observable that should yield a response code, no data
+     */
+    deletePolicy(policyTypeId: number, policyInstanceId: string): Observable<any> {
+        const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+        return this.httpClient.delete(url, { observe: 'response' });
+    }
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts
new file mode 100644 (file)
index 0000000..e7a115e
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { ConfirmDialogService } from './confirm-dialog.service';
+
+describe('ConfirmDialogService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: ConfirmDialogService = TestBed.get(ConfirmDialogService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts b/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts
new file mode 100644 (file)
index 0000000..297e673
--- /dev/null
@@ -0,0 +1,55 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { ConfirmDialogComponent } from './../../ui/confirm-dialog/confirm-dialog.component';
+import { UiService } from './ui.service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ConfirmDialogService  {
+
+  darkMode: boolean;
+  panelClass: string = "";
+
+  constructor(private dialog: MatDialog,
+    public ui: UiService) { }
+
+  openConfirmDialog(msg: string) {
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+    if (this.darkMode) {
+      this.panelClass = "dark-theme";
+    } else {
+      this.panelClass = "";
+    }
+    return this.dialog.open(ConfirmDialogComponent, {
+      panelClass: this.panelClass,
+      width: '480px',
+      position: { top: '100px' },
+      data: {
+        message: msg
+      }
+    });
+  }
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/error-dialog.service.ts b/dashboard/webapp-frontend/src/app/services/ui/error-dialog.service.ts
new file mode 100644 (file)
index 0000000..a5a12f4
--- /dev/null
@@ -0,0 +1,53 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { ErrorDialogComponent } from '../../ui/error-dialog/error-dialog.component';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatDialog } from '@angular/material/dialog';
+import { Injectable } from '@angular/core';
+import { UiService } from './ui.service';
+
+@Injectable()
+export class ErrorDialogService {
+
+  darkMode: boolean;
+  panelClass: string = "";
+  public errorMessage: string = '';
+
+  constructor(private dialog: MatDialog,
+    public ui: UiService) { }
+
+  public displayError(error: string) {
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+    if (this.darkMode) {
+      this.panelClass = "dark-theme";
+    } else {
+      this.panelClass = "";
+    }
+    return this.dialog.open(ErrorDialogComponent, {
+      panelClass: this.panelClass,
+      width: '400px',
+      position: { top: '100px' },
+      disableClose: true,
+      data: { 'errorMessage': error }
+    });
+  }
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.spec.ts
new file mode 100644 (file)
index 0000000..a73be7c
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { LoadingDialogService } from './loading-dialog.service';
+
+describe('LoadingDialogService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: LoadingDialogService = TestBed.get(LoadingDialogService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.ts b/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.ts
new file mode 100644 (file)
index 0000000..bf0830a
--- /dev/null
@@ -0,0 +1,65 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
+import { LoadingDialogComponent } from './../../ui/loading-dialog/loading-dialog.component';
+import { UiService } from './ui.service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class LoadingDialogService {
+
+  darkMode: boolean;
+  panelClass: string = "";
+
+  constructor(private dialog: MatDialog,
+    public ui: UiService) { }
+
+  private loadingDialogRef: MatDialogRef<LoadingDialogComponent>;
+
+  startLoading(msg: string) {
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+    if (this.darkMode) {
+      this.panelClass = "dark-theme";
+    } else {
+      this.panelClass = "";
+    }
+    this.loadingDialogRef = this.dialog.open(LoadingDialogComponent, {
+      panelClass: this.panelClass,
+      disableClose: true,
+      width: '480px',
+      position: { top: '100px' },
+      data: {
+        message: msg
+      }
+    });
+  }
+
+  stopLoading() {
+    this.loadingDialogRef.close()
+  }
+
+}
+
+
diff --git a/dashboard/webapp-frontend/src/app/services/ui/notification.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/notification.service.spec.ts
new file mode 100644 (file)
index 0000000..9704720
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { NotificationService } from './notification.service';
+
+describe('NotificationService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: NotificationService = TestBed.get(NotificationService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/notification.service.ts b/dashboard/webapp-frontend/src/app/services/ui/notification.service.ts
new file mode 100644 (file)
index 0000000..c7fe03f
--- /dev/null
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { ToastrService } from 'ngx-toastr';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class NotificationService {
+
+  constructor(public toastr: ToastrService) { }
+
+  successConfig = {
+    timeOut: 10000,
+    closeButton: true
+  };
+
+  warningConfig = {
+    disableTimeOut: true,
+    closeButton: true
+  };
+
+  errorConfig = {
+    disableTimeOut: true,
+    closeButton: true
+  };
+
+  success(msg: string) {
+    this.toastr.success(msg, '', this.successConfig);
+  }
+
+  warn(msg: string) {
+    this.toastr.warning(msg, '', this.warningConfig);
+  }
+
+  error(msg: string) {
+    this.toastr.error(msg, '', this.errorConfig);
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/ui.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/ui.service.spec.ts
new file mode 100644 (file)
index 0000000..3df0e9c
--- /dev/null
@@ -0,0 +1,34 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import {inject, TestBed} from '@angular/core/testing';
+
+import {UiService} from './ui.service';
+
+describe('UiService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [UiService]
+    });
+  });
+
+  it('should be created', inject([UiService], (service: UiService) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/ui.service.ts b/dashboard/webapp-frontend/src/app/services/ui/ui.service.ts
new file mode 100644 (file)
index 0000000..1e2376a
--- /dev/null
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import {Injectable} from '@angular/core';
+import {BehaviorSubject} from 'rxjs';
+
+@Injectable()
+export class UiService {
+
+  darkModeState: BehaviorSubject<boolean>;
+
+  constructor() {
+    // TODO: if the user is signed in get the default value from Firebase
+    this.darkModeState = new BehaviorSubject<boolean>(false);
+  }
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html
new file mode 100644 (file)
index 0000000..7e86ba0
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+  <div mat-dialog-title>
+  </div>
+  <div mat-dialog-content>
+    {{data.message}}
+  </div>
+  <div mat-dialog-actions class="modal-footer justify-content-center">
+    <button mat-button class="mat-raised-button" [mat-dialog-close]="false">Cancel</button>
+    <button mat-button class="mat-raised-button mat-primary" [mat-dialog-close]="true">OK</button>
+  </div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts
new file mode 100644 (file)
index 0000000..1edce02
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ConfirmDialogComponent } from './confirm-dialog.component';
+
+describe('ConfirmDialogComponent', () => {
+  let component: ConfirmDialogComponent;
+  let fixture: ComponentFixture<ConfirmDialogComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ConfirmDialogComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ConfirmDialogComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts
new file mode 100644 (file)
index 0000000..3d99530
--- /dev/null
@@ -0,0 +1,39 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+  selector: 'rd-confirm-dialog',
+  templateUrl: './confirm-dialog.component.html',
+})
+export class ConfirmDialogComponent implements OnInit {
+
+  constructor(@Inject(MAT_DIALOG_DATA) public data,
+    public dialogRef: MatDialogRef<ConfirmDialogComponent>) { }
+
+  ngOnInit() {
+  }
+
+  closeDialog() {
+    this.dialogRef.close(false);
+  }
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html
new file mode 100644 (file)
index 0000000..8516b94
--- /dev/null
@@ -0,0 +1,27 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+  
+  <div mat-dialog-content style="overflow-y: hidden;overflow:auto;">
+    <div class="error_message_text">{{data.errorMessage}}</div>    
+  </div>
+  <div mat-dialog-actions class="justify-content-center">
+    <button mat-button class="mat-raised-button mat-primary" (click)="closeDialog()">Close</button>
+  </div> 
+
diff --git a/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss
new file mode 100644 (file)
index 0000000..23de19e
--- /dev/null
@@ -0,0 +1,25 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+ .error_message_text {
+    overflow-y: overlay; 
+    max-height: 150px;
+ }
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts
new file mode 100644 (file)
index 0000000..1636c0d
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+export interface ErrorData {
+  errorMessage: string;
+}
+
+@Component({
+  selector: 'rd-error-dialog',
+  templateUrl: './error-dialog.component.html',
+  styleUrls: ['./error-dialog.component.scss']
+})
+
+export class ErrorDialogComponent {
+
+  constructor(private dialogRef: MatDialogRef<ErrorDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: ErrorData) { }
+
+  public closeDialog = () => {
+    this.dialogRef.close();
+  }
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.html b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.html
new file mode 100644 (file)
index 0000000..75d6e59
--- /dev/null
@@ -0,0 +1,27 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+
+
+  <div mat-dialog-content>
+    {{data.message}}
+  </div>
+  <mat-spinner diameter=70></mat-spinner>
+
+
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.scss b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.scss
new file mode 100644 (file)
index 0000000..fb66930
--- /dev/null
@@ -0,0 +1,23 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+mat-spinner {
+  margin: 10px
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.spec.ts
new file mode 100644 (file)
index 0000000..40fa832
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoadingDialogComponent } from './loading-dialog.component';
+
+describe('LoadingDialogComponent', () => {
+  let component: LoadingDialogComponent;
+  let fixture: ComponentFixture<LoadingDialogComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LoadingDialogComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LoadingDialogComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.ts b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.ts
new file mode 100644 (file)
index 0000000..c78ae4e
--- /dev/null
@@ -0,0 +1,36 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+
+@Component({
+  selector: 'rd-loading-dialog',
+  templateUrl: './loading-dialog.component.html',
+  styleUrls: ['./loading-dialog.component.scss']
+})
+export class LoadingDialogComponent implements OnInit {
+
+  constructor(@Inject(MAT_DIALOG_DATA) public data) { }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.html b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.html
new file mode 100644 (file)
index 0000000..197561f
--- /dev/null
@@ -0,0 +1,70 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<button type="button" mdbBtn color="default" rounded="true" data-toggle="modal" data-target="#basicExample"
+  (click)="frame.show()" mdbWavesEffect>Deploy</button>
+
+<div mdbModal #frame="mdbModal" class="modal fade left" id="frameModalTop" tabindex="-1" role="dialog"
+  aria-labelledby="myModalLabel" aria-hidden="true" (opened)="onOpened($event)">
+  <div class="modal-dialog modal-notify modal-info modal-side modal-top-left" role="document">
+    <!--Content-->
+    <div class="modal-content">
+      <!--Header-->
+      <div class="modal-header">
+        <p class="heading lead">xApp Info</p>
+
+        <button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="frame.hide()">
+          <span aria-hidden="true" class="white-text">&times;</span>
+        </button>
+      </div>
+
+      <!--Body-->
+      <div class="modal-body">
+
+        <img src="../../../assets/intelligence.png" alt=""
+          class="img-fluid">
+
+        <div class="text-center">
+          <p>xApp Manager Params</p>
+            <div class="md-form">
+                <textarea type="text" id="form8" class="md-textarea form-control" rows="1" mdbInput
+                    [formControl]="contactFormModalHelm"></textarea>
+                <label data-error="wrong" data-success="right" for="form8">Delay</label>
+            </div>
+            <div class="md-form">
+                <textarea type="text" id="form8" class="md-textarea form-control" rows="1" mdbInput
+                    [formControl]="contactFormModalHelm"></textarea>
+                <label data-error="wrong" data-success="right" for="form8">Load</label>
+            </div>
+        </div>
+      </div>
+
+      <!--Footer-->
+      <div class="modal-footer justify-content-center">
+        <a type="button" mdbBtn color="primary" class="waves-effect" mdbWavesEffect>
+            <mat-icon style="vertical-align: -21%;">launch</mat-icon> Deploy
+        </a>
+        <a type="button" mdbBtn color="primary" outline="true" class="waves-effect" mdbWavesEffect (click)="frame.hide()"
+          data-dismiss="modal">
+          <mat-icon style="vertical-align: -21%; size: 1em">close</mat-icon> Cancel</a>
+      </div>
+    </div>
+    <!--/.Content-->
+  </div>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.scss b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.spec.ts
new file mode 100644 (file)
index 0000000..0e4cf9d
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ModalEventComponent } from './modal-event.component';
+
+describe('ModalEventComponent', () => {
+  let component: ModalEventComponent;
+  let fixture: ComponentFixture<ModalEventComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ModalEventComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ModalEventComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.ts b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.ts
new file mode 100644 (file)
index 0000000..18f5163
--- /dev/null
@@ -0,0 +1,55 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, Input, OnInit , Output, EventEmitter  } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+
+@Component({
+  selector: 'rd-modal-event',
+  templateUrl: './modal-event.component.html',
+  styleUrls: ['./modal-event.component.scss']
+})
+export class ModalEventComponent implements OnInit {
+
+    public renderValue;
+
+    @Input() value;
+    @Input() rowData: any;
+    @Output() save: EventEmitter<any> = new EventEmitter();
+    contactFormModalHelm = new FormControl('', Validators.required);
+    onOpened(event: any) {
+    console.log(event);
+    }
+
+
+    constructor() {  }
+
+    ngOnInit() {
+        this.renderValue = this.value;
+    }
+
+    example() {
+        alert(this.renderValue);
+    }
+
+    onDeployxApp() {
+        this.save.emit(this.rowData);
+    }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.html b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.html
new file mode 100644 (file)
index 0000000..2c82628
--- /dev/null
@@ -0,0 +1,31 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 Nordix Foundation
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<div class="policy__card" routerLink="/policy" [ngClass]="{'add__card-dark': darkMode}">
+  <div class="header__container">
+    <span class="card__title">Policy Control</span><br><br><br>
+    <div style="color: black; size: 1em; text-align: center">
+        <mat-icon>build</mat-icon>Config</div>
+
+
+  </div>
+  <div class="body__container">
+    <img src="../../../assets/policy.png" width="250px"/>
+  </div>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss
new file mode 100644 (file)
index 0000000..1b3c349
--- /dev/null
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+.policy__card {
+  background-color: #ffffff;
+  box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+  display: grid;
+  grid-template-columns: 1fr;
+  grid-template-rows: 1fr 1fr;
+  padding: 2rem;
+  margin: 2rem;
+  width: 19rem;
+  height: 30rem;
+  justify-items: center;
+  cursor: pointer;
+  border-radius: 1.75rem;
+  animation: fadein 1.25s ease-in-out 0ms 1;
+  color: #443282;
+}
+
+.add__card-dark {
+  background: linear-gradient(to bottom, #711B86, #00057A);
+  color: white;
+}
+
+.card__title {
+  text-transform: uppercase;
+  letter-spacing: 0.1rem;
+}
+
+.body__container {
+  align-self: end;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-flow: column;
+}
+
+.add__icon {
+  width: 10rem;
+  margin-bottom: 1.15rem;
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts
new file mode 100644 (file)
index 0000000..eb678a9
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {PolicyCardComponent} from './policy-card.component';
+
+describe('PolicyCardComponent', () => {
+  let component: PolicyCardComponent;
+  let fixture: ComponentFixture<PolicyCardComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [PolicyCardComponent]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PolicyCardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts
new file mode 100644 (file)
index 0000000..174af1d
--- /dev/null
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import {Component, OnDestroy, OnInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {UiService} from '../../services/ui/ui.service';
+
+@Component({
+  selector: 'rd-policy-card',
+  templateUrl: './policy-card.component.html',
+  styleUrls: ['./policy-card.component.scss']
+})
+export class PolicyCardComponent implements OnInit, OnDestroy {
+  darkMode: boolean;
+
+  constructor(public router: Router, public ui: UiService) { }
+
+  ngOnInit() {
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+  }
+
+  ngOnDestroy() { }
+
+  openDetails() {
+    this.router.navigateByUrl('../../policy');
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.html b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.html
new file mode 100644 (file)
index 0000000..ef29a83
--- /dev/null
@@ -0,0 +1,44 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+-->
+<div mat-dialog-title>
+  Add Dashboard User
+</div>
+
+<form [formGroup]="addUserDialogForm" novalidate autocomplete="off">
+  <div mat-dialog-content>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="First Name" formControlName="firstName">
+    </mat-form-field>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="Last Name" formControlName="lastName">
+    </mat-form-field>
+  </div>
+  <div name="status">
+    <label id="request-type-radio-group-label">Status:</label>
+    <mat-radio-group aria-label="status" formControlName="status">
+      <mat-radio-button value="Active">Active</mat-radio-button>
+      <mat-radio-button value="Inactive">Inactive</mat-radio-button>
+    </mat-radio-group>
+  </div>
+  <div mat-dialog-actions class="modal-footer justify-content-center">
+    <button class="mat-raised-button" (click)="onCancel()">Cancel</button>
+    <button class="mat-raised-button mat-primary" [disabled]="!addUserDialogForm.valid" (click)="addUser(addUserDialogForm.value)" >Add</button>
+  </div>
+</form>
diff --git a/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.scss b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.scss
new file mode 100644 (file)
index 0000000..9717962
--- /dev/null
@@ -0,0 +1,23 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+.input-display-block {
+    display: block;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.ts b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.ts
new file mode 100644 (file)
index 0000000..faca105
--- /dev/null
@@ -0,0 +1,61 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialogRef } from '@angular/material/dialog';
+import { DashboardService } from '../../services/dashboard/dashboard.service';
+import { ErrorDialogService } from '../../services/ui/error-dialog.service';
+
+@Component({
+  selector: 'add-dashboard-user-dialog',
+  templateUrl: './add-dashboard-user-dialog.component.html',
+  styleUrls: ['./add-dashboard-user-dialog.component.scss']
+})
+export class AddDashboardUserDialogComponent implements OnInit {
+
+  public addUserDialogForm: FormGroup;
+
+  constructor(
+    private dialogRef: MatDialogRef<AddDashboardUserDialogComponent>,
+    private dashSvc: DashboardService,
+    private errorService: ErrorDialogService) { }
+
+  ngOnInit() {
+    this.addUserDialogForm = new FormGroup({
+      firstName: new FormControl('', [Validators.required]),
+      lastName: new FormControl('', [Validators.required]),
+      status: new FormControl('', [Validators.required])
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  public addUser = (FormValue) => {
+    if (this.addUserDialogForm.valid) {
+      // send the request to backend when it's ready
+      const aboutError = 'Not implemented yet';
+      this.errorService.displayError(aboutError);
+    }
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.html b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.html
new file mode 100644 (file)
index 0000000..f64d9df
--- /dev/null
@@ -0,0 +1,44 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+-->
+<div mat-dialog-title>
+  Edit Dashboard User
+</div>
+  
+<form [formGroup]="editUserDialogForm" novalidate autocomplete="off">
+  <div mat-dialog-content>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="First Name" formControlName="firstName" >
+    </mat-form-field>
+    <mat-form-field class="input-display-block">
+      <input matInput type="text" placeholder="Last Name" formControlName="lastName" >
+    </mat-form-field>
+  </div>
+  <div name="status">
+    <label id="request-type-radio-group-label">Status:</label>
+    <mat-radio-group aria-label="status" formControlName="status">
+      <mat-radio-button value="Active">Active</mat-radio-button>
+      <mat-radio-button value="Inactive">Inactive</mat-radio-button>
+    </mat-radio-group>
+  </div>
+  <div mat-dialog-actions class="modal-footer justify-content-center">
+    <button class="mat-raised-button" (click)="onCancel()">Cancel</button>
+    <button class="mat-raised-button mat-primary" [disabled]="!editUserDialogForm.valid" (click)="editUser(editUserDialogForm.value)">Update</button>
+  </div>
+</form>
diff --git a/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.scss b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.scss
new file mode 100644 (file)
index 0000000..9717962
--- /dev/null
@@ -0,0 +1,23 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+.input-display-block {
+    display: block;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.ts b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.ts
new file mode 100644 (file)
index 0000000..aef8860
--- /dev/null
@@ -0,0 +1,63 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { Component, Inject, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { DashboardService } from '../../services/dashboard/dashboard.service';
+import { ErrorDialogService } from '../../services/ui/error-dialog.service';
+
+
+@Component({
+  selector: 'rd-edit-app-dashboard-user-dialog',
+  templateUrl: './edit-dashboard-user-dialog.component.html',
+  styleUrls: ['./edit-dashboard-user-dialog.component.scss']
+})
+export class EditDashboardUserDialogComponent implements OnInit {
+
+  public editUserDialogForm: FormGroup;
+
+  constructor(
+    @Inject(MAT_DIALOG_DATA) public data,
+    private dialogRef: MatDialogRef<EditDashboardUserDialogComponent>,
+    private dashSvc: DashboardService,
+    private errorService: ErrorDialogService) { }
+
+  ngOnInit() {
+    this.editUserDialogForm = new FormGroup({
+      firstName: new FormControl(this.data.firstName , [Validators.required]),
+      lastName: new FormControl(this.data.lastName, [Validators.required]),
+      status: new FormControl(this.data.status, [Validators.required])
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  public editUser = (FormValue) => {
+    if (this.editUserDialogForm.valid) {
+      // send the request to backend when it's ready
+      const aboutError = 'Not implemented yet';
+      this.errorService.displayError(aboutError);
+    }
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.html b/dashboard/webapp-frontend/src/app/user/user.component.html
new file mode 100644 (file)
index 0000000..5bffc79
--- /dev/null
@@ -0,0 +1,76 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+-->
+<div class="user__section">
+  <h3 class="rd-global-page-title">Users</h3>
+  <button mat-raised-button (click)="addUser()">Add User</button>
+  <div class="spinner-container" *ngIf="dataSource.loading$ | async">
+    <mat-spinner></mat-spinner>
+  </div>
+  <table mat-table [dataSource]="dataSource" matSort class="user-table mat-elevation-z8">
+
+    <ng-container matColumnDef="loginId">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> Login ID </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.loginId}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="firstName">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> First Name </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.firstName}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="lastName">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> Last Name </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.lastName}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="active">
+      <mat-header-cell *matHeaderCellDef mat-sort-header> Active? </mat-header-cell>
+      <mat-cell *matCellDef="let element"> {{element.active}} </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="action">
+      <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
+      <mat-cell *matCellDef="let element">
+        <div class="user-button-row">
+          <button mat-icon-button (click)="editUser(element)">
+            <mat-icon matTooltip="Edit properties">edit</mat-icon>
+          </button>
+          <button mat-icon-button color="warn" (click)="deleteUser(element)">
+            <mat-icon matTooltip="Delete user">delete</mat-icon>
+          </button>
+        </div>
+      </mat-cell>
+    </ng-container>
+
+    <ng-container matColumnDef="noRecordsFound">
+      <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+    </ng-container>
+
+    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
+    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
+    <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': dataSource.rowCount > 0}"></mat-footer-row>
+
+  </table>
+
+  <div class="spinner-container" *ngIf="dataSource.loading$ | async">
+    <mat-spinner diameter=50></mat-spinner>
+  </div>
+
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.scss b/dashboard/webapp-frontend/src/app/user/user.component.scss
new file mode 100644 (file)
index 0000000..f3a5d89
--- /dev/null
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+.user__section {
+}
+
+.spinner-container {
+    height: 360px;
+    width: 390px;
+    position: fixed;
+}
+
+.spinner-container mat-spinner {
+    margin: 130px auto 0 auto;
+}
+
+.user-table {
+    width: 99%;
+    min-height: 150px;
+    margin-top: 10px;
+    background-color: transparent;
+}
+
+.user-button-row button {
+    margin-right: 5px;
+}
+
+.display-none {
+    display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.spec.ts b/dashboard/webapp-frontend/src/app/user/user.component.spec.ts
new file mode 100644 (file)
index 0000000..d4d822e
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UserComponent } from './user.component';
+
+describe('UserComponent', () => {
+  let component: AdminComponent;
+  let fixture: ComponentFixture<UserComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [UserComponent]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UserComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.ts b/dashboard/webapp-frontend/src/app/user/user.component.ts
new file mode 100644 (file)
index 0000000..a3573c0
--- /dev/null
@@ -0,0 +1,97 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { MatSort } from '@angular/material/sort';
+import { DashboardService } from '../services/dashboard/dashboard.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { EcompUser } from './../interfaces/dashboard.types';
+import { NotificationService } from './../services/ui/notification.service';
+import { UserDataSource } from './user.datasource';
+import { AddDashboardUserDialogComponent } from './add-dashboard-user-dialog/add-dashboard-user-dialog.component';
+import { EditDashboardUserDialogComponent } from './edit-dashboard-user-dialog/edit-dashboard-user-dialog.component';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+  selector: 'rd-user',
+  templateUrl: './user.component.html',
+  styleUrls: ['./user.component.scss']
+})
+
+export class UserComponent implements OnInit {
+
+  darkMode: boolean;
+  panelClass: string = "";
+  displayedColumns: string[] = ['loginId', 'firstName', 'lastName', 'active', 'action'];
+  dataSource: UserDataSource;
+  @ViewChild(MatSort, {static: true}) sort: MatSort;
+
+  constructor(
+    private dashboardSvc: DashboardService,
+    private errorService: ErrorDialogService,
+    private notificationService: NotificationService,
+    public dialog: MatDialog,
+    public ui: UiService) { }
+
+  ngOnInit() {
+    this.dataSource = new UserDataSource(this.dashboardSvc, this.sort, this.notificationService);
+    this.dataSource.loadTable();
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+  }
+
+  editUser(user: EcompUser) {
+    if (this.darkMode) {
+      this.panelClass = "dark-theme"
+    } else {
+      this.panelClass = "";
+    }
+    const dialogRef = this.dialog.open(EditDashboardUserDialogComponent, {
+      panelClass: this.panelClass,
+      width: '450px',
+      data: user
+    });
+    dialogRef.afterClosed().subscribe(result => {
+      this.dataSource.loadTable();
+    });
+  }
+
+  deleteUser() {
+    const aboutError = 'Not implemented (yet).';
+    this.errorService.displayError(aboutError);
+  }
+
+  addUser() {
+    if (this.darkMode) {
+      this.panelClass = "dark-theme"
+    } else {
+      this.panelClass = "";
+    }
+    const dialogRef = this.dialog.open(AddDashboardUserDialogComponent, {
+      panelClass: this.panelClass,
+      width: '450px'
+    });
+    dialogRef.afterClosed().subscribe(result => {
+      this.dataSource.loadTable();
+    });
+  }
+}
+
diff --git a/dashboard/webapp-frontend/src/app/user/user.datasource.ts b/dashboard/webapp-frontend/src/app/user/user.datasource.ts
new file mode 100644 (file)
index 0000000..6b7805b
--- /dev/null
@@ -0,0 +1,102 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+import { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material/sort';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { EcompUser } from '../interfaces/dashboard.types';
+import { DashboardService } from '../services/dashboard/dashboard.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class UserDataSource extends DataSource<EcompUser> {
+
+  private userSubject = new BehaviorSubject<EcompUser[]>([]);
+
+  private loadingSubject = new BehaviorSubject<boolean>(false);
+
+  public loading$ = this.loadingSubject.asObservable();
+
+  public rowCount = 1; // hide footer during intial load
+
+  constructor(private dashboardSvc: DashboardService,
+    private sort: MatSort,
+    private notificationService: NotificationService) {
+    super();
+  }
+
+  loadTable() {
+    this.loadingSubject.next(true);
+    this.dashboardSvc.getUsers()
+      .pipe(
+        catchError( (her: HttpErrorResponse) => {
+          console.log('UserDataSource failed: ' + her.message);
+          this.notificationService.error('Failed to get users: ' + her.message);
+          return of([]);
+        }),
+        finalize(() => this.loadingSubject.next(false))
+      )
+      .subscribe( (users: EcompUser[]) => {
+        this.rowCount = users.length;
+        this.userSubject.next(users);
+      });
+  }
+
+  connect(collectionViewer: CollectionViewer): Observable<EcompUser[]> {
+    const dataMutations = [
+      this.userSubject.asObservable(),
+      this.sort.sortChange
+    ];
+    return merge(...dataMutations).pipe(map(() => {
+      return this.getSortedData([...this.userSubject.getValue()]);
+    }));
+  }
+
+  disconnect(collectionViewer: CollectionViewer): void {
+    this.userSubject.complete();
+    this.loadingSubject.complete();
+  }
+
+  private getSortedData(data: EcompUser[]) {
+    if (!this.sort.active || this.sort.direction === '') {
+      return data;
+    }
+
+    return data.sort((a: EcompUser, b: EcompUser) => {
+      const isAsc = this.sort.direction === 'asc';
+      switch (this.sort.active) {
+        case 'loginId': return this.compare(a.loginId, b.loginId, isAsc);
+        case 'firstName': return this.compare(a.firstName, b.firstName, isAsc);
+        case 'lastName': return this.compare(a.lastName, b.lastName, isAsc);
+        case 'active': return this.compare(a.active, b.active, isAsc);
+        default: return 0;
+      }
+    });
+  }
+
+  private compare(a: any, b: any, isAsc: boolean) {
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+  }
+
+}
diff --git a/dashboard/webapp-frontend/src/assets/ORANlogo.png b/dashboard/webapp-frontend/src/assets/ORANlogo.png
new file mode 100644 (file)
index 0000000..4c3dfb1
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/ORANlogo.png differ
diff --git a/dashboard/webapp-frontend/src/assets/at_t.png b/dashboard/webapp-frontend/src/assets/at_t.png
new file mode 100644 (file)
index 0000000..3cced1d
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/at_t.png differ
diff --git a/dashboard/webapp-frontend/src/assets/intelligence.png b/dashboard/webapp-frontend/src/assets/intelligence.png
new file mode 100644 (file)
index 0000000..c40693e
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/intelligence.png differ
diff --git a/dashboard/webapp-frontend/src/assets/latency.png b/dashboard/webapp-frontend/src/assets/latency.png
new file mode 100644 (file)
index 0000000..874d11b
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/latency.png differ
diff --git a/dashboard/webapp-frontend/src/assets/mockdata/config.json b/dashboard/webapp-frontend/src/assets/mockdata/config.json
new file mode 100644 (file)
index 0000000..255b3fe
--- /dev/null
@@ -0,0 +1,872 @@
+[
+  {
+    "metadata": {
+      "name": "Automatic Neighbor Relation",
+      "configName": "anr-appconfig",
+      "namespace": "ricxapp"
+    },
+    "descriptor": {
+      "$id": "http://example.com/root.json",
+      "$schema": "http://json-schema.org/draft-07/schema#",
+      "definitions": {},
+      "properties": {
+        "controls": {
+          "$id": "#/properties/controls",
+          "properties": {
+            "active": {
+              "$id": "#/properties/controls/properties/active",
+              "default": false,
+              "examples": [
+                true
+              ],
+              "title": "The Active Schema",
+              "type": "boolean"
+            },
+            "interfaceId": {
+              "$id": "#/properties/controls/properties/interfaceId",
+              "properties": {
+                "globalENBId": {
+                  "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+                  "properties": {
+                    "bits": {
+                      "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/bits",
+                      "default": 0,
+                      "examples": [
+                        28
+                      ],
+                      "title": "The Bits Schema",
+                      "maximum": 1024,
+                      "type": "integer"
+                    },
+                    "id": {
+                      "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/id",
+                      "default": 0,
+                      "examples": [
+                        202251
+                      ],
+                      "title": "The Id Schema",
+                      "type": "integer"
+                    },
+                    "plmnid": {
+                      "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnid",
+                      "default": "",
+                      "examples": [
+                        "310150"
+                      ],
+                      "pattern": "^(.*)$",
+                      "title": "The Plmnid Schema",
+                      "type": "string"
+                    }
+                  },
+                  "required": [
+                    "plmnid",
+                    "id",
+                    "bits"
+                  ],
+                  "title": "The Globalenbid Schema",
+                  "type": "object"
+                }
+              },
+              "required": [
+                "globalENBId"
+              ],
+              "title": "The Interfaceid Schema",
+              "type": "object"
+            },
+            "subscription": {
+              "$id": "#/properties/controls/properties/subscription",
+              "properties": {
+                "retries": {
+                  "$id": "#/properties/controls/properties/subscription/properties/retries",
+                  "default": 0,
+                  "examples": [
+                    1
+                  ],
+                  "title": "The Retries Schema",
+                  "type": "integer"
+                },
+                "retryto": {
+                  "$id": "#/properties/controls/properties/subscription/properties/retryto",
+                  "default": 0,
+                  "examples": [
+                    2
+                  ],
+                  "title": "The Retryto Schema",
+                  "type": "integer"
+                }
+              },
+              "required": [
+                "retries",
+                "retryto"
+              ],
+              "title": "The Subscription Schema",
+              "type": "object"
+            }
+          },
+          "required": [
+            "active",
+            "subscription",
+            "interfaceId"
+          ],
+          "title": "The Controls Schema",
+          "type": "object"
+        },
+        "db": {
+          "$id": "#/properties/db",
+          "properties": {
+            "host": {
+              "$id": "#/properties/db/properties/host",
+              "default": "",
+              "examples": [
+                "localhost"
+              ],
+              "pattern": "^(.*)$",
+              "title": "The Host Schema",
+              "type": "string"
+            },
+            "namespaces": {
+              "$id": "#/properties/db/properties/namespaces",
+              "items": {
+                "$id": "#/properties/db/properties/namespaces/items",
+                "default": "",
+                "examples": [
+                  "sdl",
+                  "rnib"
+                ],
+                "pattern": "^(.*)$",
+                "title": "The Items Schema",
+                "type": "string"
+              },
+              "title": "The Namespaces Schema",
+              "type": "array"
+            },
+            "port": {
+              "$id": "#/properties/db/properties/port",
+              "default": 0,
+              "examples": [
+                6379
+              ],
+              "title": "The Port Schema",
+              "type": "integer"
+            }
+          },
+          "required": [
+            "host",
+            "port",
+            "namespaces"
+          ],
+          "title": "The Db Schema",
+          "type": "object"
+        },
+        "local": {
+          "$id": "#/properties/local",
+          "properties": {
+            "host": {
+              "$id": "#/properties/local/properties/host",
+              "default": "",
+              "examples": [
+                ":8080"
+              ],
+              "pattern": "^(.*)$",
+              "title": "The Host Schema",
+              "type": "string"
+            }
+          },
+          "required": [
+            "host"
+          ],
+          "title": "The Local Schema",
+          "type": "object"
+        },
+        "logger": {
+          "$id": "#/properties/logger",
+          "properties": {
+            "level": {
+              "$id": "#/properties/logger/properties/level",
+              "default": 0,
+              "examples": [
+                3
+              ],
+              "title": "The Level Schema",
+              "type": "integer"
+            }
+          },
+          "required": [
+            "level"
+          ],
+          "title": "The Logger Schema",
+          "type": "object"
+        },
+        "metrics": {
+          "$id": "#/properties/metrics",
+          "items": {
+            "$id": "#/properties/metrics/items",
+            "properties": {
+              "description": {
+                "$id": "#/properties/metrics/items/properties/description",
+                "default": "",
+                "examples": [
+                  "The total number of UE context creation events"
+                ],
+                "pattern": "^(.*)$",
+                "title": "The Description Schema",
+                "type": "string"
+              },
+              "enabled": {
+                "$id": "#/properties/metrics/items/properties/enabled",
+                "default": false,
+                "examples": [
+                  true
+                ],
+                "title": "The Enabled Schema",
+                "type": "boolean"
+              },
+              "name": {
+                "$id": "#/properties/metrics/items/properties/name",
+                "default": "",
+                "examples": [
+                  "UEContextCreated"
+                ],
+                "pattern": "^(.*)$",
+                "title": "The Name Schema",
+                "type": "string"
+              },
+              "type": {
+                "$id": "#/properties/metrics/items/properties/type",
+                "default": "",
+                "examples": [
+                  "counter"
+                ],
+                "pattern": "^(.*)$",
+                "title": "The Type Schema",
+                "type": "string"
+              }
+            },
+            "required": [
+              "name",
+              "type",
+              "enabled",
+              "description"
+            ],
+            "title": "The Items Schema",
+            "type": "object"
+          },
+          "title": "The Metrics Schema",
+          "type": "array"
+        },
+        "rmr": {
+          "$id": "#/properties/rmr",
+          "properties": {
+            "maxSize": {
+              "$id": "#/properties/rmr/properties/maxSize",
+              "default": 0,
+              "examples": [
+                2072
+              ],
+              "title": "The Maxsize Schema",
+              "type": "integer"
+            },
+            "numWorkers": {
+              "$id": "#/properties/rmr/properties/numWorkers",
+              "default": 0,
+              "examples": [
+                1
+              ],
+              "title": "The Numworkers Schema",
+              "type": "integer"
+            },
+            "protPort": {
+              "$id": "#/properties/rmr/properties/protPort",
+              "default": "",
+              "examples": [
+                "tcp:4560"
+              ],
+              "pattern": "^(.*)$",
+              "title": "The Protport Schema",
+              "type": "string"
+            },
+            "rxMessages": {
+              "$id": "#/properties/rmr/properties/rxMessages",
+              "items": {
+                "$id": "#/properties/rmr/properties/rxMessages/items",
+                "default": "",
+                "examples": [
+                  "RIC_SUB_RESP",
+                  "RIC_SUB_FAILURE",
+                  "RIC_SUB_DEL_RESP",
+                  "RIC_SUB_DEL_FAILURE",
+                  "RIC_INDICATION"
+                ],
+                "pattern": "^(.*)$",
+                "title": "The Items Schema",
+                "type": "string"
+              },
+              "title": "The Rxmessages Schema",
+              "type": "array"
+            },
+            "txMessages": {
+              "$id": "#/properties/rmr/properties/txMessages",
+              "items": {
+                "$id": "#/properties/rmr/properties/txMessages/items",
+                "default": "",
+                "examples": [
+                  "RIC_SUB_REQ",
+                  "RIC_SUB_DEL_REQ"
+                ],
+                "pattern": "^(.*)$",
+                "title": "The Items Schema",
+                "type": "string"
+              },
+              "title": "The Txmessages Schema",
+              "type": "array"
+            }
+          },
+          "required": [
+            "protPort",
+            "maxSize",
+            "numWorkers",
+            "txMessages",
+            "rxMessages"
+          ],
+          "title": "The Rmr Schema",
+          "type": "object"
+        }
+      },
+      "required": [
+        "local",
+        "logger",
+        "rmr",
+        "db",
+        "controls",
+        "metrics"
+      ],
+      "title": "The Root Schema",
+      "type": "object"
+    },
+    "config": {
+      "controls": {
+        "active": true,
+        "interfaceId": {
+          "globalENBId": {
+            "bits": 28,
+            "id": 202251,
+            "plmnid": "310150"
+          }
+        },
+        "subscription": {
+          "retries": 1,
+          "retryto": 2
+        }
+      },
+      "db": {
+        "host": "localhost",
+        "namespaces": [
+          "sdl",
+          "rnib"
+        ],
+        "port": 6379
+      },
+      "local": {
+        "host": ":8080"
+      },
+      "logger": {
+        "level": 3
+      },
+      "metrics": [
+        {
+          "description": "The total number of UE context creation events",
+          "enabled": true,
+          "name": "UEContextCreated",
+          "type": "counter"
+        },
+        {
+          "description": "The total number of UE context release events",
+          "enabled": true,
+          "name": "UEContextReleased",
+          "type": "counter"
+        }
+      ],
+      "rmr": {
+        "maxSize": 2072,
+        "numWorkers": 1,
+        "protPort": "tcp:4560",
+        "rxMessages": [
+          "RIC_SUB_RESP",
+          "RIC_SUB_FAILURE",
+          "RIC_SUB_DEL_RESP",
+          "RIC_SUB_DEL_FAILURE",
+          "RIC_INDICATION"
+        ],
+        "txMessages": [
+          "RIC_SUB_REQ",
+          "RIC_SUB_DEL_REQ"
+        ]
+      }
+    },
+    "layout": [
+      {
+        "key": "controls.active",
+        "title": "Active"
+      },
+      {
+        "key": "controls.interfaceId.globalENBId",
+        "title": "Global ENB Id"
+      },
+      {
+        "type": "flex",
+        "flex-flow": "row wrap",
+        "items": [
+          {
+            "key": "controls.interfaceId.globalENBId.plmnid",
+            "title": "Plmn Id"
+          },
+          {
+            "key": "controls.interfaceId.globalENBId.id",
+            "title": "Id"
+
+          },
+          {
+            "key": "controls.interfaceId.globalENBId.bits",
+            "title": "Bits"
+          }
+        ]
+      },
+      {
+        "key": "controls.subscription",
+        "title": "Subscription"
+      },
+      {
+        "type": "flex",
+        "flex-flow": "row wrap",
+        "items": [
+          {
+            "key": "controls.subscription.retries",
+            "title": "Retries"
+          },
+          {
+            "key": "controls.subscription.retryto",
+            "title": "Retry to"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "metadata": {
+      "name": "UE Event Collector",
+      "configName": "UEEC-appconfig",
+      "namespace": "ricxapp"
+    },
+    "descriptor": {
+      "definitions": {},
+      "$schema": "http://json-schema.org/draft-07/schema#",
+      "$id": "http://example.com/root.json",
+      "type": "object",
+      "title": "The Root Schema",
+      "required": [
+        "local",
+        "logger",
+        "rmr",
+        "db",
+        "controls",
+        "metrics"
+      ],
+      "properties": {
+        "local": {
+          "$id": "#/properties/local",
+          "type": "object",
+          "title": "The Local Schema",
+          "required": [
+            "host"
+          ],
+          "properties": {
+            "host": {
+              "$id": "#/properties/local/properties/host",
+              "type": "string",
+              "title": "The Host Schema",
+              "default": "",
+              "examples": [
+                ":8080"
+              ],
+              "pattern": "^(.*)$"
+            }
+          }
+        },
+        "logger": {
+          "$id": "#/properties/logger",
+          "type": "object",
+          "title": "The Logger Schema",
+          "required": [
+            "level"
+          ],
+          "properties": {
+            "level": {
+              "$id": "#/properties/logger/properties/level",
+              "type": "integer",
+              "title": "The Level Schema",
+              "default": 0,
+              "examples": [
+                3
+              ]
+            }
+          }
+        },
+        "rmr": {
+          "$id": "#/properties/rmr",
+          "type": "object",
+          "title": "The Rmr Schema",
+          "required": [
+            "protPort",
+            "maxSize",
+            "numWorkers",
+            "txMessages",
+            "rxMessages"
+          ],
+          "properties": {
+            "protPort": {
+              "$id": "#/properties/rmr/properties/protPort",
+              "type": "string",
+              "title": "The Protport Schema",
+              "default": "",
+              "examples": [
+                "tcp:4560"
+              ],
+              "pattern": "^(.*)$"
+            },
+            "maxSize": {
+              "$id": "#/properties/rmr/properties/maxSize",
+              "type": "integer",
+              "title": "The Maxsize Schema",
+              "default": 0,
+              "examples": [
+                2072
+              ]
+            },
+            "numWorkers": {
+              "$id": "#/properties/rmr/properties/numWorkers",
+              "type": "integer",
+              "title": "The Numworkers Schema",
+              "default": 0,
+              "examples": [
+                1
+              ]
+            },
+            "txMessages": {
+              "$id": "#/properties/rmr/properties/txMessages",
+              "type": "array",
+              "title": "The Txmessages Schema",
+              "items": {
+                "$id": "#/properties/rmr/properties/txMessages/items",
+                "type": "string",
+                "title": "The Items Schema",
+                "default": "",
+                "examples": [
+                  "RIC_SUB_REQ",
+                  "RIC_SUB_DEL_REQ"
+                ],
+                "pattern": "^(.*)$"
+              }
+            },
+            "rxMessages": {
+              "$id": "#/properties/rmr/properties/rxMessages",
+              "type": "array",
+              "title": "The Rxmessages Schema",
+              "items": {
+                "$id": "#/properties/rmr/properties/rxMessages/items",
+                "type": "string",
+                "title": "The Items Schema",
+                "default": "",
+                "examples": [
+                  "RIC_SUB_RESP",
+                  "RIC_SUB_FAILURE",
+                  "RIC_SUB_DEL_RESP",
+                  "RIC_SUB_DEL_FAILURE",
+                  "RIC_INDICATION"
+                ],
+                "pattern": "^(.*)$"
+              }
+            }
+          }
+        },
+        "db": {
+          "$id": "#/properties/db",
+          "type": "object",
+          "title": "The Db Schema",
+          "required": [
+            "host",
+            "port",
+            "namespaces"
+          ],
+          "properties": {
+            "host": {
+              "$id": "#/properties/db/properties/host",
+              "type": "string",
+              "title": "The Host Schema",
+              "default": "",
+              "examples": [
+                "localhost"
+              ],
+              "pattern": "^(.*)$"
+            },
+            "port": {
+              "$id": "#/properties/db/properties/port",
+              "type": "integer",
+              "title": "The Port Schema",
+              "default": 0,
+              "examples": [
+                6379
+              ]
+            },
+            "namespaces": {
+              "$id": "#/properties/db/properties/namespaces",
+              "type": "array",
+              "title": "The Namespaces Schema",
+              "items": {
+                "$id": "#/properties/db/properties/namespaces/items",
+                "type": "string",
+                "title": "The Items Schema",
+                "default": "",
+                "examples": [
+                  "sdl",
+                  "rnib"
+                ],
+                "pattern": "^(.*)$"
+              }
+            }
+          }
+        },
+        "controls": {
+          "$id": "#/properties/controls",
+          "type": "object",
+          "title": "The Controls Schema",
+          "required": [
+            "active",
+            "requestorId",
+            "ranFunctionId",
+            "ricActionId",
+            "interfaceId"
+          ],
+          "properties": {
+            "active": {
+              "$id": "#/properties/controls/properties/active",
+              "type": "boolean",
+              "title": "The Active Schema",
+              "default": false,
+              "examples": [
+                true
+              ]
+            },
+            "requestorId": {
+              "$id": "#/properties/controls/properties/requestorId",
+              "type": "integer",
+              "title": "The Requestorid Schema",
+              "default": 0,
+              "examples": [
+                66
+              ]
+            },
+            "ranFunctionId": {
+              "$id": "#/properties/controls/properties/ranFunctionId",
+              "type": "integer",
+              "title": "The Ranfunctionid Schema",
+              "default": 0,
+              "examples": [
+                1
+              ]
+            },
+            "ricActionId": {
+              "$id": "#/properties/controls/properties/ricActionId",
+              "type": "integer",
+              "title": "The Ricactionid Schema",
+              "default": 0,
+              "examples": [
+                0
+              ]
+            },
+            "interfaceId": {
+              "$id": "#/properties/controls/properties/interfaceId",
+              "type": "object",
+              "title": "The Interfaceid Schema",
+              "required": [
+                "globalENBId"
+              ],
+              "properties": {
+                "globalENBId": {
+                  "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+                  "type": "object",
+                  "title": "The Globalenbid Schema",
+                  "required": [
+                    "plmnId",
+                    "eNBId"
+                  ],
+                  "properties": {
+                    "plmnId": {
+                      "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnId",
+                      "type": "string",
+                      "title": "The Plmnid Schema",
+                      "default": "",
+                      "examples": [
+                        "43962"
+                      ],
+                      "pattern": "^(.*)$"
+                    },
+                    "eNBId": {
+                      "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/eNBId",
+                      "type": "string",
+                      "title": "The Enbid Schema",
+                      "default": "",
+                      "examples": [
+                        "43962"
+                      ],
+                      "pattern": "^(.*)$"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "metrics": {
+          "$id": "#/properties/metrics",
+          "type": "array",
+          "title": "The Metrics Schema",
+          "items": {
+            "$id": "#/properties/metrics/items",
+            "type": "object",
+            "title": "The Items Schema",
+            "required": [
+              "name",
+              "type",
+              "enabled",
+              "description"
+            ],
+            "properties": {
+              "name": {
+                "$id": "#/properties/metrics/items/properties/name",
+                "type": "string",
+                "title": "The Name Schema",
+                "default": "",
+                "examples": [
+                  "UEContextCreated"
+                ],
+                "pattern": "^(.*)$"
+              },
+              "type": {
+                "$id": "#/properties/metrics/items/properties/type",
+                "type": "string",
+                "title": "The Type Schema",
+                "default": "",
+                "examples": [
+                  "counter"
+                ],
+                "pattern": "^(.*)$"
+              },
+              "enabled": {
+                "$id": "#/properties/metrics/items/properties/enabled",
+                "type": "boolean",
+                "title": "The Enabled Schema",
+                "default": false,
+                "examples": [
+                  true
+                ]
+              },
+              "description": {
+                "$id": "#/properties/metrics/items/properties/description",
+                "type": "string",
+                "title": "The Description Schema",
+                "default": "",
+                "examples": [
+                  "The total number of UE context creation events"
+                ],
+                "pattern": "^(.*)$"
+              }
+            }
+          }
+        }
+      }
+    },
+    "config": {
+      "local": {
+        "host": ":8080"
+      },
+      "logger": {
+        "level": 3
+      },
+      "rmr": {
+        "protPort": "tcp:4560",
+        "maxSize": 2072,
+        "numWorkers": 1,
+        "txMessages": [ "RIC_SUB_REQ", "RIC_SUB_DEL_REQ" ],
+        "rxMessages": [ "RIC_SUB_RESP", "RIC_SUB_FAILURE", "RIC_SUB_DEL_RESP", "RIC_SUB_DEL_FAILURE", "RIC_INDICATION" ]
+      },
+      "db": {
+        "host": "localhost",
+        "port": 6379,
+        "namespaces": [ "sdl", "rnib" ]
+      },
+      "controls": {
+        "active": true,
+        "requestorId": 66,
+        "ranFunctionId": 1,
+        "ricActionId": 0,
+        "interfaceId": {
+          "globalENBId": {
+            "plmnId": "43962",
+            "eNBId": "43962"
+          }
+        }
+      },
+      "metrics": [
+        {
+          "name": "UEContextCreated",
+          "type": "counter",
+          "enabled": true,
+          "description": "The total number of UE context creation events"
+        },
+        {
+          "name": "UEContextReleased",
+          "type": "counter",
+          "enabled": true,
+          "description": "The total number of UE context release events"
+        }
+      ]
+    },
+    "layout": [
+      {
+        "key": "controls.active",
+        "title": "Active"
+      },
+      {
+        "key": "controls.requestorId",
+        "title": "Requestor Id"
+      },
+      {
+        "key": "controls.ranFunctionId",
+        "title": "RAN Function Id"
+      },
+      {
+        "key": "controls.ricActionId",
+        "title": "RIC Action Id"
+      },
+      {
+        "key": "controls.interfaceId.globalENBId",
+        "title": "Global ENB Id"
+      },
+      {
+        "type": "flex",
+        "flex-flow": "row wrap",
+        "items": [
+          {
+            "key": "controls.interfaceId.globalENBId.plmnId",
+            "title": "Plmn Id"
+          },
+          {
+            "key": "controls.interfaceId.globalENBId.eNBId",
+            "title": "ENB Id"
+
+          }
+        ]
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/assets/mockdata/db.json b/dashboard/webapp-frontend/src/assets/mockdata/db.json
new file mode 100644 (file)
index 0000000..31bd473
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "config": [
+    {
+      "id": "jsonURL",
+      "value": "http://localhost:3000"
+    },
+    {
+      "id": "host",
+      "value": "http://localhost:3000"
+    },
+    {
+      "id": "metricspath",
+      "value": "/a1ric/metrics"
+    },
+    {
+      "id": "delaypath",
+      "value": "/a1ric/delay"
+    },
+    {
+      "id": "loadpath",
+      "value": "/a1ric/load"
+    }
+  ],
+  "metrics": {
+    "latency": 11,
+    "load": 100,
+    "ricload": 100,
+    "time": 123
+  },
+  "delay": {
+    "delay": 64877
+  },
+  "load": {
+    "load": 1
+  }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/assets/mockdata/routes.json b/dashboard/webapp-frontend/src/assets/mockdata/routes.json
new file mode 100644 (file)
index 0000000..0745958
--- /dev/null
@@ -0,0 +1,4 @@
+{
+  "/a1ric/*": "/$1",
+  "/:resource/:id/show": "/:resource/:id"
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/assets/oran-logo.png b/dashboard/webapp-frontend/src/assets/oran-logo.png
new file mode 100644 (file)
index 0000000..c3b6ce5
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/oran-logo.png differ
diff --git a/dashboard/webapp-frontend/src/assets/policy.png b/dashboard/webapp-frontend/src/assets/policy.png
new file mode 100644 (file)
index 0000000..11df15d
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/policy.png differ
diff --git a/dashboard/webapp-frontend/src/assets/profile_default.png b/dashboard/webapp-frontend/src/assets/profile_default.png
new file mode 100644 (file)
index 0000000..2b90bf0
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/profile_default.png differ
diff --git a/dashboard/webapp-frontend/src/assets/xAppControl.png b/dashboard/webapp-frontend/src/assets/xAppControl.png
new file mode 100644 (file)
index 0000000..9458a85
Binary files /dev/null and b/dashboard/webapp-frontend/src/assets/xAppControl.png differ
diff --git a/dashboard/webapp-frontend/src/environments/environment.prod.ts b/dashboard/webapp-frontend/src/environments/environment.prod.ts
new file mode 100644 (file)
index 0000000..58489a2
--- /dev/null
@@ -0,0 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+export const environment = {
+  production: true
+};
diff --git a/dashboard/webapp-frontend/src/environments/environment.ts b/dashboard/webapp-frontend/src/environments/environment.ts
new file mode 100644 (file)
index 0000000..c7873a3
--- /dev/null
@@ -0,0 +1,35 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+  production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.
diff --git a/dashboard/webapp-frontend/src/favicon.ico b/dashboard/webapp-frontend/src/favicon.ico
new file mode 100644 (file)
index 0000000..00b0fd0
Binary files /dev/null and b/dashboard/webapp-frontend/src/favicon.ico differ
diff --git a/dashboard/webapp-frontend/src/index.html b/dashboard/webapp-frontend/src/index.html
new file mode 100644 (file)
index 0000000..f423e92
--- /dev/null
@@ -0,0 +1,32 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2019 AT&T Intellectual Property
+  %%
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ========================LICENSE_END===================================
+  -->
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Non RT RIC Dashboard</title>
+    <base href="/">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="icon" type="image/x-icon" href="assets/oran-logo.png">
+  </head>
+  <body>
+    <rd-root></rd-root>
+  </body>
+</html>
diff --git a/dashboard/webapp-frontend/src/karma.conf.js b/dashboard/webapp-frontend/src/karma.conf.js
new file mode 100644 (file)
index 0000000..a125d9b
--- /dev/null
@@ -0,0 +1,50 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+  config.set({
+    basePath: '',
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-chrome-launcher'),
+      require('karma-jasmine-html-reporter'),
+      require('karma-coverage-istanbul-reporter'),
+      require('@angular-devkit/build-angular/plugins/karma')
+    ],
+    client: {
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
+    },
+    coverageIstanbulReporter: {
+      dir: require('path').join(__dirname, '../coverage'),
+      reports: ['html', 'lcovonly', 'text-summary'],
+      fixWebpackSourcePaths: true
+    },
+    reporters: ['progress', 'kjhtml'],
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['Chrome'],
+    singleRun: false
+  });
+};
diff --git a/dashboard/webapp-frontend/src/main.ts b/dashboard/webapp-frontend/src/main.ts
new file mode 100644 (file)
index 0000000..60c66e4
--- /dev/null
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { RdModule } from './app/rd.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+  enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(RdModule)
+  .catch(err => console.error(err));
diff --git a/dashboard/webapp-frontend/src/polyfills.ts b/dashboard/webapp-frontend/src/polyfills.ts
new file mode 100644 (file)
index 0000000..09fd208
--- /dev/null
@@ -0,0 +1,104 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
+ *  This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
+ */
+
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ *  with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ *  (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone';  // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/dashboard/webapp-frontend/src/styles.scss b/dashboard/webapp-frontend/src/styles.scss
new file mode 100644 (file)
index 0000000..c74cf88
--- /dev/null
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+@import '~bootstrap/dist/css/bootstrap.min.css';
+@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
+@import './styles/dark-theme';
+
+/* for sidenav to take a whole page */
+html, body { 
+    margin: 0;
+    height: 100%;
+    font-family: Helvetica, Arial, sans-serif;
+}
+
+/* notification */
+.confirm-dialog-container span.content-span {
+  padding: 35px 16px 20px 16px;
+  text-align: center;
+  font-size: 20px;
+}
+
+.rd-global-page-title {
+  margin-left: 0.5%;
+  color: #432c85;
+  font-size: 25px;
+  font-weight: 100;
+}
diff --git a/dashboard/webapp-frontend/src/styles/dark-theme.scss b/dashboard/webapp-frontend/src/styles/dark-theme.scss
new file mode 100644 (file)
index 0000000..8cc0ece
--- /dev/null
@@ -0,0 +1,36 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+
+@import '~@angular/material/theming';
+@include mat-core();
+
+.dark-theme {
+  color: white;
+  $dark-primary: mat-palette($mat-yellow);
+  $dark-accent: mat-palette($mat-amber, A400, A100, A700);
+  $dark-warn: mat-palette($mat-red);
+  $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
+
+  @include angular-material-theme($dark-theme);
+}
+
+.dark-theme .rd-global-page-title {
+  color:white;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/test.ts b/dashboard/webapp-frontend/src/test.ts
new file mode 100644 (file)
index 0000000..e9d6a82
--- /dev/null
@@ -0,0 +1,39 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================LICENSE_END===================================
+ */
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/dashboard/webapp-frontend/src/tsconfig.app.json b/dashboard/webapp-frontend/src/tsconfig.app.json
new file mode 100644 (file)
index 0000000..190fd30
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/app",
+    "types": []
+  },
+  "exclude": [
+    "test.ts",
+    "**/*.spec.ts"
+  ]
+}
diff --git a/dashboard/webapp-frontend/src/tsconfig.spec.json b/dashboard/webapp-frontend/src/tsconfig.spec.json
new file mode 100644 (file)
index 0000000..de77336
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/spec",
+    "types": [
+      "jasmine",
+      "node"
+    ]
+  },
+  "files": [
+    "test.ts",
+    "polyfills.ts"
+  ],
+  "include": [
+    "**/*.spec.ts",
+    "**/*.d.ts"
+  ]
+}
diff --git a/dashboard/webapp-frontend/src/tslint.json b/dashboard/webapp-frontend/src/tslint.json
new file mode 100644 (file)
index 0000000..30581c6
--- /dev/null
@@ -0,0 +1,17 @@
+{
+    "extends": "../tslint.json",
+    "rules": {
+        "directive-selector": [
+            true,
+            "attribute",
+            "rd",
+            "camelCase"
+        ],
+        "component-selector": [
+            true,
+            "element",
+            "rd",
+            "kebab-case"
+        ]
+    }
+}
diff --git a/dashboard/webapp-frontend/tsconfig.json b/dashboard/webapp-frontend/tsconfig.json
new file mode 100644 (file)
index 0000000..457bef4
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "baseUrl": "./",
+    "downlevelIteration": true,
+    "outDir": "./dist/out-tsc",
+    "sourceMap": true,
+    "declaration": false,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "importHelpers": true,
+    "target": "es5",
+    "typeRoots": [
+      "node_modules/@types"
+    ],
+    "lib": [
+      "es2018",
+      "dom"
+    ]
+  }
+}
diff --git a/dashboard/webapp-frontend/tslint.json b/dashboard/webapp-frontend/tslint.json
new file mode 100644 (file)
index 0000000..a919351
--- /dev/null
@@ -0,0 +1,131 @@
+{
+  "rulesDirectory": [
+    "codelyzer"
+  ],
+  "rules": {
+    "arrow-return-shorthand": true,
+    "callable-types": true,
+    "class-name": true,
+    "comment-format": [
+      true,
+      "check-space"
+    ],
+    "curly": true,
+    "deprecation": {
+      "severity": "warn"
+    },
+    "eofline": true,
+    "forin": true,
+    "import-blacklist": [
+      true,
+      "rxjs/Rx"
+    ],
+    "import-spacing": true,
+    "indent": [
+      true,
+      "spaces"
+    ],
+    "interface-over-type-literal": true,
+    "label-position": true,
+    "max-line-length": [
+      true,
+      140
+    ],
+    "member-access": false,
+    "member-ordering": [
+      true,
+      {
+        "order": [
+          "static-field",
+          "instance-field",
+          "static-method",
+          "instance-method"
+        ]
+      }
+    ],
+    "no-arg": true,
+    "no-bitwise": true,
+    "no-console": [
+      true,
+      "debug",
+      "info",
+      "time",
+      "timeEnd",
+      "trace"
+    ],
+    "no-construct": true,
+    "no-debugger": true,
+    "no-duplicate-super": true,
+    "no-empty": false,
+    "no-empty-interface": true,
+    "no-eval": true,
+    "no-inferrable-types": [
+      true,
+      "ignore-params"
+    ],
+    "no-misused-new": true,
+    "no-non-null-assertion": true,
+    "no-redundant-jsdoc": true,
+    "no-shadowed-variable": true,
+    "no-string-literal": false,
+    "no-string-throw": true,
+    "no-switch-case-fall-through": true,
+    "no-trailing-whitespace": true,
+    "no-unnecessary-initializer": true,
+    "no-unused-expression": true,
+    "no-use-before-declare": true,
+    "no-var-keyword": true,
+    "object-literal-sort-keys": false,
+    "one-line": [
+      true,
+      "check-open-brace",
+      "check-catch",
+      "check-else",
+      "check-whitespace"
+    ],
+    "prefer-const": true,
+    "quotemark": [
+      true,
+      "single"
+    ],
+    "radix": true,
+    "semicolon": [
+      true,
+      "always"
+    ],
+    "triple-equals": [
+      true,
+      "allow-null-check"
+    ],
+    "typedef-whitespace": [
+      true,
+      {
+        "call-signature": "nospace",
+        "index-signature": "nospace",
+        "parameter": "nospace",
+        "property-declaration": "nospace",
+        "variable-declaration": "nospace"
+      }
+    ],
+    "unified-signatures": true,
+    "variable-name": false,
+    "whitespace": [
+      true,
+      "check-branch",
+      "check-decl",
+      "check-operator",
+      "check-separator",
+      "check-type"
+    ],
+    "no-output-on-prefix": true,
+    "no-inputs-metadata-property": true,
+    "no-outputs-metadata-property": true,
+    "no-host-metadata-property": true,
+    "no-input-rename": true,
+    "no-output-rename": true,
+    "use-lifecycle-interface": true,
+    "use-pipe-transform-interface": true,
+    "component-class-suffix": true,
+    "directive-class-suffix": true
+  }
+}