]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: add telemetry report component
authorTatjana Dehler <tdehler@suse.com>
Fri, 21 Feb 2020 09:08:10 +0000 (10:08 +0100)
committerTatjana Dehler <tdehler@suse.com>
Wed, 29 Apr 2020 14:34:00 +0000 (16:34 +0200)
Add a telemetry component in order to give the user the
possibility to configure the telemetry module in a more
guided fashion. The component offers broader explanations,
shows a preview of the generated report and asks the user
to accept the license before enabling the module.

Fixes: https://tracker.ceph.com/issues/43956
Signed-off-by: Tatjana Dehler <tdehler@suse.com>
19 files changed:
doc/mgr/telemetry.rst
qa/suites/rados/dashboard/tasks/dashboard.yaml
qa/tasks/mgr/dashboard/test_telemetry.py [new file with mode: 0644]
src/pybind/mgr/dashboard/controllers/__init__.py
src/pybind/mgr/dashboard/controllers/telemetry.py [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/package-lock.json
src/pybind/mgr/dashboard/frontend/package.json
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.ts [new file with mode: 0644]

index 64c2d0f0e5b4a794195042107934449a31c8ca9a..6eaaa5c44923527dff8358faba139ead43ff6324 100644 (file)
@@ -55,7 +55,7 @@ deployed, the version of Ceph, the distribution of the hosts and other
 parameters which help the project to gain a better understanding of
 the way Ceph is used.
 
-Data is sent over HTTPS to *telemetry.ceph.com*.
+Data is sent secured to *https://telemetry.ceph.com*.
 
 Sample report
 -------------
index e61294eeed9a3cff1aba54b694631190cf1c2536..f210fc1c86df8e90f8208f04d2045bb25762b0c3 100644 (file)
@@ -52,4 +52,5 @@ tasks:
         - tasks.mgr.dashboard.test_role
         - tasks.mgr.dashboard.test_settings
         - tasks.mgr.dashboard.test_summary
+        - tasks.mgr.dashboard.test_telemetry
         - tasks.mgr.dashboard.test_user
diff --git a/qa/tasks/mgr/dashboard/test_telemetry.py b/qa/tasks/mgr/dashboard/test_telemetry.py
new file mode 100644 (file)
index 0000000..840ccdd
--- /dev/null
@@ -0,0 +1,89 @@
+from .helper import DashboardTestCase, JObj
+
+
+class TelemetryTest(DashboardTestCase):
+
+    pre_enabled_status = True
+
+    @classmethod
+    def setUpClass(cls):
+        super(TelemetryTest, cls).setUpClass()
+        data = cls._get('/api/mgr/module/telemetry')
+        cls.pre_enabled_status = data['enabled']
+
+    @classmethod
+    def tearDownClass(cls):
+        if cls.pre_enabled_status:
+            cls._enable_module()
+        else:
+            cls._disable_module()
+        super(TelemetryTest, cls).tearDownClass()
+
+    def test_disable_module(self):
+        self._enable_module()
+        self._check_telemetry_enabled(True)
+        self._disable_module()
+        self._check_telemetry_enabled(False)
+
+    def test_enable_module_correct_license(self):
+        self._disable_module()
+        self._check_telemetry_enabled(False)
+
+        self._put('/api/telemetry', {
+            'enable': True,
+            'license_name': 'sharing-1-0'
+        })
+        self.assertStatus(200)
+        self._check_telemetry_enabled(True)
+
+    def test_enable_module_empty_license(self):
+        self._disable_module()
+        self._check_telemetry_enabled(False)
+
+        self._put('/api/telemetry', {
+            'enable': True,
+            'license_name': ''
+        })
+        self.assertStatus(400)
+        self.assertError(code='telemetry_enable_license_missing')
+        self._check_telemetry_enabled(False)
+
+    def test_enable_module_invalid_license(self):
+        self._disable_module()
+        self._check_telemetry_enabled(False)
+
+        self._put('/api/telemetry', {
+            'enable': True,
+            'license_name': 'invalid-license'
+        })
+        self.assertStatus(400)
+        self.assertError(code='telemetry_enable_license_missing')
+        self._check_telemetry_enabled(False)
+
+    def test_get_report(self):
+        self._enable_module()
+        data = self._get('/api/telemetry/report')
+        self.assertStatus(200)
+        schema = JObj({
+            'report': JObj({}, allow_unknown=True),
+            'device_report': JObj({}, allow_unknown=True)
+        })
+        self.assertSchema(data, schema)
+
+    @classmethod
+    def _enable_module(cls):
+        cls._put('/api/telemetry', {
+            'enable': True,
+            'license_name': 'sharing-1-0'
+        })
+
+    @classmethod
+    def _disable_module(cls):
+        cls._put('/api/telemetry', {
+            'enable': False
+        })
+
+    def _check_telemetry_enabled(self, enabled):
+        data = self._get('/api/mgr/module/telemetry')
+        self.assertStatus(200)
+        self.assertEqual(data['enabled'], enabled)
index 3bd4cba8d738a56b47e38ec03b670fadfad649d2..a72ed73ce0a4f883ba117ec18e7a7e4a28818c03 100644 (file)
@@ -717,6 +717,7 @@ class RESTController(BaseController):
     * bulk_delete()
     * get(key)
     * set(data, key)
+    * singleton_set(data)
     * delete(key)
 
     Test with curl:
@@ -749,7 +750,8 @@ class RESTController(BaseController):
         ('bulk_delete', {'method': 'DELETE', 'resource': False, 'status': 204}),
         ('get', {'method': 'GET', 'resource': True, 'status': 200}),
         ('delete', {'method': 'DELETE', 'resource': True, 'status': 204}),
-        ('set', {'method': 'PUT', 'resource': True, 'status': 200})
+        ('set', {'method': 'PUT', 'resource': True, 'status': 200}),
+        ('singleton_set', {'method': 'PUT', 'resource': False, 'status': 200})
     ])
 
     @classmethod
diff --git a/src/pybind/mgr/dashboard/controllers/telemetry.py b/src/pybind/mgr/dashboard/controllers/telemetry.py
new file mode 100644 (file)
index 0000000..c68432e
--- /dev/null
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+from . import ApiController, RESTController
+from .. import mgr
+from ..exceptions import DashboardException
+from ..security import Scope
+
+
+@ApiController('/telemetry', Scope.CONFIG_OPT)
+class Telemetry(RESTController):
+
+    @RESTController.Collection('GET')
+    def report(self):
+        """
+        Get Ceph and device report data
+        :return: Ceph and device report data
+        :rtype: dict
+        """
+        return mgr.remote('telemetry', 'get_report', 'all')
+
+    def singleton_set(self, enable=True, license_name=None):
+        """
+        Enables or disables sending data collected by the Telemetry
+        module.
+        :param enable: Enable or disable sending data
+        :type enable: bool
+        :param license_name: License string e.g. 'sharing-1-0' to
+            make sure the user is aware of and accepts the license
+            for sharing Telemetry data.
+        :type license_name: string
+        """
+        if enable:
+            if not license_name or (license_name != 'sharing-1-0'):
+                raise DashboardException(
+                    code='telemetry_enable_license_missing',
+                    msg='Telemetry data is licensed under the Community Data License Agreement - '
+                        'Sharing - Version 1.0 (https://cdla.io/sharing-1-0/). To enable, add '
+                        '{"license": "sharing-1-0"} to the request payload.'
+                )
+            mgr.remote('telemetry', 'on')
+        else:
+            mgr.remote('telemetry', 'off')
index 139d7887769e2245414c9d6decfeb8f48a5a8aa8..85556b75569439cc1857e9c0009d66d4cb6058c1 100644 (file)
           }
         },
         "fsevents": {
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "node-pre-gyp": "*"
-          },
           "dependencies": {
-            "abbrev": {
+            "npm-bundled": {
               "version": "1.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ansi-regex": {
-              "version": "2.1.1",
-              "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "npm-normalize-package-bin": "^1.0.1"
+              }
             },
-            "aproba": {
-              "version": "1.2.0",
+            "semver": {
+              "version": "5.7.1",
               "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",
+            "safe-buffer": {
+              "version": "5.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "brace-expansion": {
-              "version": "1.1.11",
+            "process-nextick-args": {
+              "version": "2.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "npmlog": {
+              "version": "4.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "balanced-match": "^1.0.0",
-                "concat-map": "0.0.1"
+                "are-we-there-yet": "~1.1.2",
+                "console-control-strings": "~1.1.0",
+                "gauge": "~2.7.3",
+                "set-blocking": "~2.0.0"
               }
             },
-            "chownr": {
-              "version": "1.1.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "code-point-at": {
-              "version": "1.1.0",
+            "safer-buffer": {
+              "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "concat-map": {
-              "version": "0.0.1",
+            "wide-align": {
+              "version": "1.1.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "string-width": "^1.0.2 || 2"
+              }
             },
-            "console-control-strings": {
-              "version": "1.1.0",
+            "once": {
+              "version": "1.4.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "wrappy": "1"
+              }
             },
-            "core-util-is": {
-              "version": "1.0.2",
+            "strip-ansi": {
+              "version": "3.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "ansi-regex": "^2.0.0"
+              }
             },
-            "debug": {
-              "version": "3.2.6",
+            "nopt": {
+              "version": "4.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ms": "^2.1.1"
+                "abbrev": "1",
+                "osenv": "^0.1.4"
               }
             },
-            "deep-extend": {
-              "version": "0.6.0",
+            "console-control-strings": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "delegates": {
+            "fs.realpath": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "detect-libc": {
-              "version": "1.0.3",
+            "tar": {
+              "version": "4.4.13",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": 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"
+              }
             },
-            "fs-minipass": {
-              "version": "1.2.7",
+            "brace-expansion": {
+              "version": "1.1.11",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minipass": "^2.6.0"
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
               }
             },
-            "fs.realpath": {
-              "version": "1.0.0",
+            "os-tmpdir": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "gauge": {
-              "version": "2.7.4",
+            "minipass": {
+              "version": "2.9.0",
               "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"
+                "safe-buffer": "^5.1.2",
+                "yallist": "^3.0.0"
               }
             },
-            "glob": {
-              "version": "7.1.6",
+            "yallist": {
+              "version": "3.1.1",
               "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"
-              }
+              "optional": true
             },
-            "has-unicode": {
-              "version": "2.0.1",
+            "code-point-at": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "iconv-lite": {
-              "version": "0.4.24",
+            "rc": {
+              "version": "1.2.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safer-buffer": ">= 2.1.2 < 3"
+                "deep-extend": "^0.6.0",
+                "ini": "~1.3.0",
+                "minimist": "^1.2.0",
+                "strip-json-comments": "~2.0.1"
               }
             },
-            "ignore-walk": {
-              "version": "3.0.3",
+            "deep-extend": {
+              "version": "0.6.0",
+              "bundled": true,
+              "optional": true
+            },
+            "minizlib": {
+              "version": "1.3.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minimatch": "^3.0.4"
+                "minipass": "^2.9.0"
               }
             },
-            "inflight": {
-              "version": "1.0.6",
+            "osenv": {
+              "version": "0.1.5",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "once": "^1.3.0",
-                "wrappy": "1"
+                "os-homedir": "^1.0.0",
+                "os-tmpdir": "^1.0.0"
               }
             },
-            "inherits": {
-              "version": "2.0.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ini": {
-              "version": "1.3.5",
+            "signal-exit": {
+              "version": "3.0.2",
               "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",
+            "sax": {
+              "version": "1.2.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "minimatch": {
-              "version": "3.0.4",
+            "node-pre-gyp": {
+              "version": "0.14.0",
               "bundled": true,
-              "dev": true,
-              "optional": true,
               "requires": {
-                "brace-expansion": "^1.1.7"
+                "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.4.2"
               }
             },
-            "minimist": {
-              "version": "1.2.5",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "minipass": {
-              "version": "2.9.0",
+            "gauge": {
+              "version": "2.7.4",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safe-buffer": "^5.1.2",
-                "yallist": "^3.0.0"
+                "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"
               }
             },
-            "minizlib": {
-              "version": "1.3.3",
+            "ansi-regex": {
+              "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minipass": "^2.9.0"
-              }
+              "optional": true
             },
-            "mkdirp": {
-              "version": "0.5.3",
+            "has-unicode": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minimist": "^1.2.5"
-              }
+              "optional": true
+            },
+            "os-homedir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true
             },
             "ms": {
               "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "needle": {
-              "version": "2.3.3",
+            "readable-stream": {
+              "version": "2.3.7",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "debug": "^3.2.6",
-                "iconv-lite": "^0.4.4",
-                "sax": "^1.2.4"
+                "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"
               }
             },
-            "node-pre-gyp": {
-              "version": "0.14.0",
+            "aproba": {
+              "version": "1.2.0",
               "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.4.2"
-              }
+              "optional": true
             },
-            "nopt": {
-              "version": "4.0.3",
+            "strip-json-comments": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "abbrev": "1",
-                "osenv": "^0.1.4"
-              }
+              "optional": true
             },
-            "npm-bundled": {
-              "version": "1.1.1",
+            "inherits": {
+              "version": "2.0.4",
+              "bundled": true,
+              "optional": true
+            },
+            "concat-map": {
+              "version": "0.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "debug": {
+              "version": "3.2.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "npm-normalize-package-bin": "^1.0.1"
+                "ms": "^2.1.1"
               }
             },
-            "npm-normalize-package-bin": {
-              "version": "1.0.1",
+            "util-deprecate": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true
+            },
+            "chownr": {
+              "version": "1.1.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
             "npm-packlist": {
               "version": "1.4.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
                 "ignore-walk": "^3.0.1",
                 "npm-normalize-package-bin": "^1.0.1"
               }
             },
-            "npmlog": {
-              "version": "4.1.2",
+            "balanced-match": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            },
+            "string-width": {
+              "version": "1.0.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"
+                "code-point-at": "^1.0.0",
+                "is-fullwidth-code-point": "^1.0.0",
+                "strip-ansi": "^3.0.0"
               }
             },
-            "number-is-nan": {
+            "npm-normalize-package-bin": {
               "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",
+            "needle": {
+              "version": "2.3.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "wrappy": "1"
+                "debug": "^3.2.6",
+                "iconv-lite": "^0.4.4",
+                "sax": "^1.2.4"
               }
             },
-            "os-homedir": {
-              "version": "1.0.2",
+            "ini": {
+              "version": "1.3.5",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "os-tmpdir": {
-              "version": "1.0.2",
+            "minimatch": {
+              "version": "3.0.4",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "brace-expansion": "^1.1.7"
+              }
             },
-            "osenv": {
-              "version": "0.1.5",
+            "mkdirp": {
+              "version": "0.5.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "os-homedir": "^1.0.0",
-                "os-tmpdir": "^1.0.0"
+                "minimist": "^1.2.5"
               }
             },
-            "path-is-absolute": {
-              "version": "1.0.1",
+            "set-blocking": {
+              "version": "2.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "process-nextick-args": {
-              "version": "2.0.1",
+            "wrappy": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "rc": {
-              "version": "1.2.8",
+            "fs-minipass": {
+              "version": "1.2.7",
               "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"
+                "minipass": "^2.6.0"
               }
             },
-            "readable-stream": {
-              "version": "2.3.7",
+            "core-util-is": {
+              "version": "1.0.2",
               "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"
-              }
+              "optional": true
+            },
+            "number-is-nan": {
+              "version": "1.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "isarray": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
             },
             "rimraf": {
               "version": "2.7.1",
               "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",
+            "minimist": {
+              "version": "1.2.5",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "semver": {
-              "version": "5.7.1",
+            "glob": {
+              "version": "7.1.6",
               "bundled": true,
-              "dev": true,
-              "optional": 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"
+              }
             },
-            "set-blocking": {
-              "version": "2.0.0",
+            "object-assign": {
+              "version": "4.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "signal-exit": {
-              "version": "3.0.2",
+            "delegates": {
+              "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "string-width": {
-              "version": "1.0.2",
+            "ignore-walk": {
+              "version": "3.0.3",
               "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"
+                "minimatch": "^3.0.4"
               }
             },
-            "string_decoder": {
-              "version": "1.1.1",
+            "inflight": {
+              "version": "1.0.6",
               "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"
+                "once": "^1.3.0",
+                "wrappy": "1"
               }
             },
-            "strip-json-comments": {
-              "version": "2.0.1",
+            "path-is-absolute": {
+              "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "tar": {
-              "version": "4.4.13",
+            "iconv-lite": {
+              "version": "0.4.24",
               "bundled": true,
-              "dev": true,
               "optional": 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"
+                "safer-buffer": ">= 2.1.2 < 3"
               }
             },
-            "util-deprecate": {
-              "version": "1.0.2",
+            "detect-libc": {
+              "version": "1.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "wide-align": {
-              "version": "1.1.3",
+            "string_decoder": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "string-width": "^1.0.2 || 2"
+                "safe-buffer": "~5.1.0"
               }
             },
-            "wrappy": {
-              "version": "1.0.2",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "yallist": {
-              "version": "3.1.1",
+            "abbrev": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             }
           },
           }
         },
         "mem": {
-          "dev": true,
           "requires": {
             "map-age-cleaner": "^0.1.1",
             "mimic-fn": "^2.0.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
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "os-locale": {
           "version": "3.1.0",
       "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
       "dev": true
     },
+    "@types/file-saver": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz",
+      "integrity": "sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw=="
+    },
     "@types/glob": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
         }
       }
     },
+    "file-saver": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
+      "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
+    },
     "fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
-    "fsevents": {
-      "dev": true,
-      "optional": true,
-      "version": "2.1.2"
-    },
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
           }
         },
         "mem": {
-          "dev": true,
           "requires": {
             "map-age-cleaner": "^0.1.1",
             "mimic-fn": "^2.0.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
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "os-locale": {
           "version": "3.1.0",
           }
         },
         "fsevents": {
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "node-pre-gyp": "*"
-          },
           "dependencies": {
-            "abbrev": {
+            "npm-bundled": {
               "version": "1.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ansi-regex": {
-              "version": "2.1.1",
-              "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "npm-normalize-package-bin": "^1.0.1"
+              }
             },
-            "aproba": {
-              "version": "1.2.0",
+            "semver": {
+              "version": "5.7.1",
               "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",
+            "safe-buffer": {
+              "version": "5.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "brace-expansion": {
-              "version": "1.1.11",
+            "process-nextick-args": {
+              "version": "2.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "npmlog": {
+              "version": "4.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "balanced-match": "^1.0.0",
-                "concat-map": "0.0.1"
+                "are-we-there-yet": "~1.1.2",
+                "console-control-strings": "~1.1.0",
+                "gauge": "~2.7.3",
+                "set-blocking": "~2.0.0"
               }
             },
-            "chownr": {
-              "version": "1.1.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "code-point-at": {
-              "version": "1.1.0",
+            "safer-buffer": {
+              "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "concat-map": {
-              "version": "0.0.1",
+            "wide-align": {
+              "version": "1.1.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "string-width": "^1.0.2 || 2"
+              }
             },
-            "console-control-strings": {
-              "version": "1.1.0",
+            "once": {
+              "version": "1.4.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "wrappy": "1"
+              }
             },
-            "core-util-is": {
-              "version": "1.0.2",
+            "strip-ansi": {
+              "version": "3.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "ansi-regex": "^2.0.0"
+              }
             },
-            "debug": {
-              "version": "3.2.6",
+            "nopt": {
+              "version": "4.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ms": "^2.1.1"
+                "abbrev": "1",
+                "osenv": "^0.1.4"
               }
             },
-            "deep-extend": {
-              "version": "0.6.0",
+            "console-control-strings": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "delegates": {
+            "fs.realpath": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "detect-libc": {
-              "version": "1.0.3",
+            "tar": {
+              "version": "4.4.13",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": 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"
+              }
             },
-            "fs-minipass": {
-              "version": "1.2.7",
+            "brace-expansion": {
+              "version": "1.1.11",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minipass": "^2.6.0"
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
               }
             },
-            "fs.realpath": {
-              "version": "1.0.0",
+            "os-tmpdir": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "gauge": {
-              "version": "2.7.4",
+            "minipass": {
+              "version": "2.9.0",
               "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"
+                "safe-buffer": "^5.1.2",
+                "yallist": "^3.0.0"
               }
             },
-            "glob": {
-              "version": "7.1.6",
+            "yallist": {
+              "version": "3.1.1",
+              "bundled": true,
+              "optional": true
+            },
+            "code-point-at": {
+              "version": "1.1.0",
+              "bundled": true,
+              "optional": true
+            },
+            "rc": {
+              "version": "1.2.8",
               "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"
+                "deep-extend": "^0.6.0",
+                "ini": "~1.3.0",
+                "minimist": "^1.2.0",
+                "strip-json-comments": "~2.0.1"
               }
             },
-            "has-unicode": {
-              "version": "2.0.1",
+            "deep-extend": {
+              "version": "0.6.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "iconv-lite": {
-              "version": "0.4.24",
+            "minizlib": {
+              "version": "1.3.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safer-buffer": ">= 2.1.2 < 3"
+                "minipass": "^2.9.0"
               }
             },
-            "ignore-walk": {
-              "version": "3.0.3",
+            "osenv": {
+              "version": "0.1.5",
               "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"
+                "os-homedir": "^1.0.0",
+                "os-tmpdir": "^1.0.0"
               }
             },
-            "inherits": {
-              "version": "2.0.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ini": {
-              "version": "1.3.5",
+            "signal-exit": {
+              "version": "3.0.2",
               "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",
+            "sax": {
+              "version": "1.2.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "minimatch": {
-              "version": "3.0.4",
+            "node-pre-gyp": {
+              "version": "0.14.0",
               "bundled": true,
-              "dev": true,
-              "optional": true,
               "requires": {
-                "brace-expansion": "^1.1.7"
+                "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.4.2"
               }
             },
-            "minimist": {
-              "version": "1.2.5",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "minipass": {
-              "version": "2.9.0",
+            "gauge": {
+              "version": "2.7.4",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safe-buffer": "^5.1.2",
-                "yallist": "^3.0.0"
+                "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"
               }
             },
-            "minizlib": {
-              "version": "1.3.3",
+            "ansi-regex": {
+              "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minipass": "^2.9.0"
-              }
+              "optional": true
             },
-            "mkdirp": {
-              "version": "0.5.3",
+            "has-unicode": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minimist": "^1.2.5"
-              }
+              "optional": true
+            },
+            "os-homedir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true
             },
             "ms": {
               "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "needle": {
-              "version": "2.3.3",
+            "readable-stream": {
+              "version": "2.3.7",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "debug": "^3.2.6",
-                "iconv-lite": "^0.4.4",
-                "sax": "^1.2.4"
+                "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"
               }
             },
-            "node-pre-gyp": {
-              "version": "0.14.0",
+            "aproba": {
+              "version": "1.2.0",
               "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.4.2"
-              }
+              "optional": true
             },
-            "nopt": {
-              "version": "4.0.3",
+            "strip-json-comments": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "abbrev": "1",
-                "osenv": "^0.1.4"
-              }
+              "optional": true
             },
-            "npm-bundled": {
-              "version": "1.1.1",
+            "inherits": {
+              "version": "2.0.4",
+              "bundled": true,
+              "optional": true
+            },
+            "concat-map": {
+              "version": "0.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "debug": {
+              "version": "3.2.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "npm-normalize-package-bin": "^1.0.1"
+                "ms": "^2.1.1"
               }
             },
-            "npm-normalize-package-bin": {
-              "version": "1.0.1",
+            "util-deprecate": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true
+            },
+            "chownr": {
+              "version": "1.1.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
             "npm-packlist": {
               "version": "1.4.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
                 "ignore-walk": "^3.0.1",
                 "npm-normalize-package-bin": "^1.0.1"
               }
             },
-            "npmlog": {
-              "version": "4.1.2",
+            "balanced-match": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            },
+            "string-width": {
+              "version": "1.0.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"
+                "code-point-at": "^1.0.0",
+                "is-fullwidth-code-point": "^1.0.0",
+                "strip-ansi": "^3.0.0"
               }
             },
-            "number-is-nan": {
+            "npm-normalize-package-bin": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "object-assign": {
-              "version": "4.1.1",
+            "needle": {
+              "version": "2.3.3",
+              "bundled": true,
+              "optional": true,
+              "requires": {
+                "debug": "^3.2.6",
+                "iconv-lite": "^0.4.4",
+                "sax": "^1.2.4"
+              }
+            },
+            "ini": {
+              "version": "1.3.5",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "once": {
-              "version": "1.4.0",
+            "minimatch": {
+              "version": "3.0.4",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "wrappy": "1"
+                "brace-expansion": "^1.1.7"
               }
             },
-            "os-homedir": {
-              "version": "1.0.2",
+            "mkdirp": {
+              "version": "0.5.3",
+              "bundled": true,
+              "optional": true,
+              "requires": {
+                "minimist": "^1.2.5"
+              }
+            },
+            "set-blocking": {
+              "version": "2.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "os-tmpdir": {
+            "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "osenv": {
-              "version": "0.1.5",
+            "fs-minipass": {
+              "version": "1.2.7",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "os-homedir": "^1.0.0",
-                "os-tmpdir": "^1.0.0"
+                "minipass": "^2.6.0"
               }
             },
-            "path-is-absolute": {
+            "core-util-is": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true
+            },
+            "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "process-nextick-args": {
-              "version": "2.0.1",
+            "isarray": {
+              "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "rc": {
-              "version": "1.2.8",
+            "rimraf": {
+              "version": "2.7.1",
               "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"
+                "glob": "^7.1.3"
               }
             },
-            "readable-stream": {
-              "version": "2.3.7",
+            "minimist": {
+              "version": "1.2.5",
               "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"
-              }
+              "optional": true
             },
-            "rimraf": {
-              "version": "2.7.1",
+            "glob": {
+              "version": "7.1.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "glob": "^7.1.3"
+                "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"
               }
             },
-            "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.7.1",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "set-blocking": {
-              "version": "2.0.0",
+            "object-assign": {
+              "version": "4.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "signal-exit": {
-              "version": "3.0.2",
+            "delegates": {
+              "version": "1.0.0",
               "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",
+            "ignore-walk": {
+              "version": "3.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safe-buffer": "~5.1.0"
+                "minimatch": "^3.0.4"
               }
             },
-            "strip-ansi": {
-              "version": "3.0.1",
+            "inflight": {
+              "version": "1.0.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ansi-regex": "^2.0.0"
+                "once": "^1.3.0",
+                "wrappy": "1"
               }
             },
-            "strip-json-comments": {
-              "version": "2.0.1",
+            "path-is-absolute": {
+              "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "tar": {
-              "version": "4.4.13",
+            "iconv-lite": {
+              "version": "0.4.24",
               "bundled": true,
-              "dev": true,
               "optional": 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"
+                "safer-buffer": ">= 2.1.2 < 3"
               }
             },
-            "util-deprecate": {
-              "version": "1.0.2",
+            "detect-libc": {
+              "version": "1.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "wide-align": {
-              "version": "1.1.3",
+            "string_decoder": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "string-width": "^1.0.2 || 2"
+                "safe-buffer": "~5.1.0"
               }
             },
-            "wrappy": {
-              "version": "1.0.2",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "yallist": {
-              "version": "3.1.1",
+            "abbrev": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             }
           },
       "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"
       }
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
       "dev": true
     },
-    "mem": {
-      "requires": {
-        "mimic-fn": "^1.0.0"
-      },
-      "version": "4.3.0"
-    },
     "memory-fs": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
     "mimic-fn": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
-      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
+      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+      "dev": true
     },
     "mini-css-extract-plugin": {
       "version": "0.8.0",
       "dependencies": {
         "mem": {
           "version": "4.3.0"
+        },
+        "mimic-fn": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         }
       }
     },
     "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
+      "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww="
     },
     "p-each-series": {
       "version": "2.1.0",
     "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
+      "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg=="
     },
     "p-limit": {
       "version": "1.3.0",
           }
         },
         "fsevents": {
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "node-pre-gyp": "*"
-          },
           "dependencies": {
-            "abbrev": {
+            "npm-bundled": {
               "version": "1.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ansi-regex": {
-              "version": "2.1.1",
-              "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "npm-normalize-package-bin": "^1.0.1"
+              }
             },
-            "aproba": {
-              "version": "1.2.0",
+            "semver": {
+              "version": "5.7.1",
               "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",
+            "safe-buffer": {
+              "version": "5.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "brace-expansion": {
-              "version": "1.1.11",
+            "process-nextick-args": {
+              "version": "2.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "npmlog": {
+              "version": "4.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "balanced-match": "^1.0.0",
-                "concat-map": "0.0.1"
+                "are-we-there-yet": "~1.1.2",
+                "console-control-strings": "~1.1.0",
+                "gauge": "~2.7.3",
+                "set-blocking": "~2.0.0"
               }
             },
-            "chownr": {
-              "version": "1.1.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "code-point-at": {
-              "version": "1.1.0",
+            "safer-buffer": {
+              "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "concat-map": {
-              "version": "0.0.1",
+            "wide-align": {
+              "version": "1.1.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "string-width": "^1.0.2 || 2"
+              }
             },
-            "console-control-strings": {
-              "version": "1.1.0",
+            "once": {
+              "version": "1.4.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "wrappy": "1"
+              }
             },
-            "core-util-is": {
-              "version": "1.0.2",
+            "strip-ansi": {
+              "version": "3.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "ansi-regex": "^2.0.0"
+              }
             },
-            "debug": {
-              "version": "3.2.6",
+            "nopt": {
+              "version": "4.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ms": "^2.1.1"
+                "abbrev": "1",
+                "osenv": "^0.1.4"
               }
             },
-            "deep-extend": {
-              "version": "0.6.0",
+            "console-control-strings": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "delegates": {
+            "fs.realpath": {
               "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.7",
+            "tar": {
+              "version": "4.4.13",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minipass": "^2.6.0"
-              }
-            },
-            "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"
+                "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"
               }
             },
-            "glob": {
-              "version": "7.1.6",
+            "brace-expansion": {
+              "version": "1.1.11",
               "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"
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
               }
             },
-            "has-unicode": {
-              "version": "2.0.1",
+            "os-tmpdir": {
+              "version": "1.0.2",
               "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.3",
-              "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minimatch": "^3.0.4"
-              }
-            },
-            "inflight": {
-              "version": "1.0.6",
+            "minipass": {
+              "version": "2.9.0",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "once": "^1.3.0",
-                "wrappy": "1"
+                "safe-buffer": "^5.1.2",
+                "yallist": "^3.0.0"
               }
             },
-            "inherits": {
-              "version": "2.0.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ini": {
-              "version": "1.3.5",
+            "yallist": {
+              "version": "3.1.1",
               "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",
+            "code-point-at": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "minimatch": {
-              "version": "3.0.4",
+            "rc": {
+              "version": "1.2.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "brace-expansion": "^1.1.7"
+                "deep-extend": "^0.6.0",
+                "ini": "~1.3.0",
+                "minimist": "^1.2.0",
+                "strip-json-comments": "~2.0.1"
               }
             },
-            "minimist": {
-              "version": "1.2.5",
+            "deep-extend": {
+              "version": "0.6.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "minipass": {
-              "version": "2.9.0",
-              "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "safe-buffer": "^5.1.2",
-                "yallist": "^3.0.0"
-              }
-            },
             "minizlib": {
               "version": "1.3.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
                 "minipass": "^2.9.0"
               }
             },
-            "mkdirp": {
-              "version": "0.5.3",
+            "osenv": {
+              "version": "0.1.5",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minimist": "^1.2.5"
+                "os-homedir": "^1.0.0",
+                "os-tmpdir": "^1.0.0"
               }
             },
-            "ms": {
-              "version": "2.1.2",
+            "signal-exit": {
+              "version": "3.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "needle": {
-              "version": "2.3.3",
+            "is-fullwidth-code-point": {
+              "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "debug": "^3.2.6",
-                "iconv-lite": "^0.4.4",
-                "sax": "^1.2.4"
+                "number-is-nan": "^1.0.0"
               }
             },
+            "sax": {
+              "version": "1.2.4",
+              "bundled": true,
+              "optional": true
+            },
             "node-pre-gyp": {
               "version": "0.14.0",
               "bundled": true,
-              "dev": true,
-              "optional": true,
               "requires": {
                 "detect-libc": "^1.0.2",
                 "mkdirp": "^0.5.1",
                 "tar": "^4.4.2"
               }
             },
-            "nopt": {
-              "version": "4.0.3",
+            "gauge": {
+              "version": "2.7.4",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "abbrev": "1",
-                "osenv": "^0.1.4"
+                "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"
               }
             },
-            "npm-bundled": {
-              "version": "1.1.1",
+            "ansi-regex": {
+              "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "npm-normalize-package-bin": "^1.0.1"
-              }
+              "optional": true
             },
-            "npm-normalize-package-bin": {
-              "version": "1.0.1",
+            "has-unicode": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "npm-packlist": {
-              "version": "1.4.8",
+            "os-homedir": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "ignore-walk": "^3.0.1",
-                "npm-bundled": "^1.0.1",
-                "npm-normalize-package-bin": "^1.0.1"
-              }
+              "optional": true
             },
-            "npmlog": {
-              "version": "4.1.2",
+            "ms": {
+              "version": "2.1.2",
+              "bundled": true,
+              "optional": true
+            },
+            "readable-stream": {
+              "version": "2.3.7",
               "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"
+                "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"
               }
             },
-            "number-is-nan": {
-              "version": "1.0.1",
+            "aproba": {
+              "version": "1.2.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "object-assign": {
-              "version": "4.1.1",
+            "strip-json-comments": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "once": {
-              "version": "1.4.0",
+            "inherits": {
+              "version": "2.0.4",
+              "bundled": true,
+              "optional": true
+            },
+            "concat-map": {
+              "version": "0.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "debug": {
+              "version": "3.2.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "wrappy": "1"
+                "ms": "^2.1.1"
               }
             },
-            "os-homedir": {
+            "util-deprecate": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "os-tmpdir": {
-              "version": "1.0.2",
+            "chownr": {
+              "version": "1.1.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "osenv": {
-              "version": "0.1.5",
+            "npm-packlist": {
+              "version": "1.4.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "os-homedir": "^1.0.0",
-                "os-tmpdir": "^1.0.0"
-              }
+                "ignore-walk": "^3.0.1",
+                "npm-bundled": "^1.0.1",
+                "npm-normalize-package-bin": "^1.0.1"
+              }
             },
-            "path-is-absolute": {
-              "version": "1.0.1",
+            "balanced-match": {
+              "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "process-nextick-args": {
-              "version": "2.0.1",
+            "string-width": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true,
+              "requires": {
+                "code-point-at": "^1.0.0",
+                "is-fullwidth-code-point": "^1.0.0",
+                "strip-ansi": "^3.0.0"
+              }
+            },
+            "npm-normalize-package-bin": {
+              "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "rc": {
-              "version": "1.2.8",
+            "needle": {
+              "version": "2.3.3",
               "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"
+                "debug": "^3.2.6",
+                "iconv-lite": "^0.4.4",
+                "sax": "^1.2.4"
               }
             },
-            "readable-stream": {
-              "version": "2.3.7",
+            "ini": {
+              "version": "1.3.5",
+              "bundled": true,
+              "optional": true
+            },
+            "minimatch": {
+              "version": "3.0.4",
               "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"
+                "brace-expansion": "^1.1.7"
               }
             },
-            "rimraf": {
-              "version": "2.7.1",
+            "mkdirp": {
+              "version": "0.5.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "glob": "^7.1.3"
+                "minimist": "^1.2.5"
               }
             },
-            "safe-buffer": {
-              "version": "5.1.2",
+            "set-blocking": {
+              "version": "2.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "safer-buffer": {
-              "version": "2.1.2",
+            "wrappy": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "sax": {
-              "version": "1.2.4",
+            "fs-minipass": {
+              "version": "1.2.7",
+              "bundled": true,
+              "optional": true,
+              "requires": {
+                "minipass": "^2.6.0"
+              }
+            },
+            "core-util-is": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "semver": {
-              "version": "5.7.1",
+            "number-is-nan": {
+              "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "set-blocking": {
-              "version": "2.0.0",
+            "isarray": {
+              "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "signal-exit": {
-              "version": "3.0.2",
+            "rimraf": {
+              "version": "2.7.1",
+              "bundled": true,
+              "optional": true,
+              "requires": {
+                "glob": "^7.1.3"
+              }
+            },
+            "minimist": {
+              "version": "1.2.5",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "string-width": {
-              "version": "1.0.2",
+            "glob": {
+              "version": "7.1.6",
               "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"
+                "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"
               }
             },
-            "string_decoder": {
-              "version": "1.1.1",
+            "object-assign": {
+              "version": "4.1.1",
+              "bundled": true,
+              "optional": true
+            },
+            "delegates": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            },
+            "ignore-walk": {
+              "version": "3.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safe-buffer": "~5.1.0"
+                "minimatch": "^3.0.4"
               }
             },
-            "strip-ansi": {
-              "version": "3.0.1",
+            "inflight": {
+              "version": "1.0.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ansi-regex": "^2.0.0"
+                "once": "^1.3.0",
+                "wrappy": "1"
               }
             },
-            "strip-json-comments": {
-              "version": "2.0.1",
+            "path-is-absolute": {
+              "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "tar": {
-              "version": "4.4.13",
+            "iconv-lite": {
+              "version": "0.4.24",
               "bundled": true,
-              "dev": true,
               "optional": 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"
+                "safer-buffer": ">= 2.1.2 < 3"
               }
             },
-            "util-deprecate": {
-              "version": "1.0.2",
+            "detect-libc": {
+              "version": "1.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "wide-align": {
-              "version": "1.1.3",
+            "string_decoder": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "string-width": "^1.0.2 || 2"
+                "safe-buffer": "~5.1.0"
               }
             },
-            "wrappy": {
-              "version": "1.0.2",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "yallist": {
-              "version": "3.1.1",
+            "abbrev": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             }
           },
           }
         },
         "fsevents": {
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "node-pre-gyp": "*"
-          },
           "dependencies": {
-            "abbrev": {
+            "npm-bundled": {
               "version": "1.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ansi-regex": {
-              "version": "2.1.1",
-              "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "npm-normalize-package-bin": "^1.0.1"
+              }
             },
-            "aproba": {
-              "version": "1.2.0",
+            "semver": {
+              "version": "5.7.1",
               "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",
+            "safe-buffer": {
+              "version": "5.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "brace-expansion": {
-              "version": "1.1.11",
+            "process-nextick-args": {
+              "version": "2.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "npmlog": {
+              "version": "4.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "balanced-match": "^1.0.0",
-                "concat-map": "0.0.1"
+                "are-we-there-yet": "~1.1.2",
+                "console-control-strings": "~1.1.0",
+                "gauge": "~2.7.3",
+                "set-blocking": "~2.0.0"
               }
             },
-            "chownr": {
-              "version": "1.1.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "code-point-at": {
-              "version": "1.1.0",
+            "safer-buffer": {
+              "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "concat-map": {
-              "version": "0.0.1",
+            "wide-align": {
+              "version": "1.1.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "string-width": "^1.0.2 || 2"
+              }
             },
-            "console-control-strings": {
-              "version": "1.1.0",
+            "once": {
+              "version": "1.4.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "wrappy": "1"
+              }
             },
-            "core-util-is": {
-              "version": "1.0.2",
+            "strip-ansi": {
+              "version": "3.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "ansi-regex": "^2.0.0"
+              }
             },
-            "debug": {
-              "version": "3.2.6",
+            "nopt": {
+              "version": "4.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ms": "^2.1.1"
+                "abbrev": "1",
+                "osenv": "^0.1.4"
               }
             },
-            "deep-extend": {
-              "version": "0.6.0",
+            "console-control-strings": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "delegates": {
+            "fs.realpath": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "detect-libc": {
-              "version": "1.0.3",
+            "tar": {
+              "version": "4.4.13",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": 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"
+              }
             },
-            "fs-minipass": {
-              "version": "1.2.7",
+            "brace-expansion": {
+              "version": "1.1.11",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minipass": "^2.6.0"
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
               }
             },
-            "fs.realpath": {
-              "version": "1.0.0",
+            "os-tmpdir": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "gauge": {
-              "version": "2.7.4",
+            "minipass": {
+              "version": "2.9.0",
               "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"
+                "safe-buffer": "^5.1.2",
+                "yallist": "^3.0.0"
               }
             },
-            "glob": {
-              "version": "7.1.6",
+            "yallist": {
+              "version": "3.1.1",
               "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"
-              }
+              "optional": true
             },
-            "has-unicode": {
-              "version": "2.0.1",
+            "code-point-at": {
+              "version": "1.1.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "iconv-lite": {
-              "version": "0.4.24",
+            "rc": {
+              "version": "1.2.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safer-buffer": ">= 2.1.2 < 3"
+                "deep-extend": "^0.6.0",
+                "ini": "~1.3.0",
+                "minimist": "^1.2.0",
+                "strip-json-comments": "~2.0.1"
               }
             },
-            "ignore-walk": {
-              "version": "3.0.3",
+            "deep-extend": {
+              "version": "0.6.0",
+              "bundled": true,
+              "optional": true
+            },
+            "minizlib": {
+              "version": "1.3.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "minimatch": "^3.0.4"
+                "minipass": "^2.9.0"
               }
             },
-            "inflight": {
-              "version": "1.0.6",
+            "osenv": {
+              "version": "0.1.5",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "once": "^1.3.0",
-                "wrappy": "1"
+                "os-homedir": "^1.0.0",
+                "os-tmpdir": "^1.0.0"
               }
             },
-            "inherits": {
-              "version": "2.0.4",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "ini": {
-              "version": "1.3.5",
+            "signal-exit": {
+              "version": "3.0.2",
               "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",
+            "sax": {
+              "version": "1.2.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "minimatch": {
-              "version": "3.0.4",
+            "node-pre-gyp": {
+              "version": "0.14.0",
               "bundled": true,
-              "dev": true,
-              "optional": true,
               "requires": {
-                "brace-expansion": "^1.1.7"
+                "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.4.2"
               }
             },
-            "minimist": {
-              "version": "1.2.5",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "minipass": {
-              "version": "2.9.0",
+            "gauge": {
+              "version": "2.7.4",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safe-buffer": "^5.1.2",
-                "yallist": "^3.0.0"
+                "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"
               }
             },
-            "minizlib": {
-              "version": "1.3.3",
+            "ansi-regex": {
+              "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minipass": "^2.9.0"
-              }
+              "optional": true
             },
-            "mkdirp": {
-              "version": "0.5.3",
+            "has-unicode": {
+              "version": "2.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "minimist": "^1.2.5"
-              }
+              "optional": true
+            },
+            "os-homedir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "optional": true
             },
             "ms": {
               "version": "2.1.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "needle": {
-              "version": "2.3.3",
+            "readable-stream": {
+              "version": "2.3.7",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "debug": "^3.2.6",
-                "iconv-lite": "^0.4.4",
-                "sax": "^1.2.4"
+                "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"
               }
             },
-            "node-pre-gyp": {
-              "version": "0.14.0",
+            "aproba": {
+              "version": "1.2.0",
+              "bundled": true,
+              "optional": true
+            },
+            "strip-json-comments": {
+              "version": "2.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "inherits": {
+              "version": "2.0.4",
+              "bundled": true,
+              "optional": true
+            },
+            "concat-map": {
+              "version": "0.0.1",
               "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.4.2"
-              }
+              "optional": true
             },
-            "nopt": {
-              "version": "4.0.3",
+            "debug": {
+              "version": "3.2.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "abbrev": "1",
-                "osenv": "^0.1.4"
+                "ms": "^2.1.1"
               }
             },
-            "npm-bundled": {
-              "version": "1.1.1",
+            "util-deprecate": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true,
-              "requires": {
-                "npm-normalize-package-bin": "^1.0.1"
-              }
+              "optional": true
             },
-            "npm-normalize-package-bin": {
-              "version": "1.0.1",
+            "chownr": {
+              "version": "1.1.4",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
             "npm-packlist": {
               "version": "1.4.8",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
                 "ignore-walk": "^3.0.1",
                 "npm-normalize-package-bin": "^1.0.1"
               }
             },
-            "npmlog": {
-              "version": "4.1.2",
+            "balanced-match": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            },
+            "string-width": {
+              "version": "1.0.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"
+                "code-point-at": "^1.0.0",
+                "is-fullwidth-code-point": "^1.0.0",
+                "strip-ansi": "^3.0.0"
               }
             },
-            "number-is-nan": {
+            "npm-normalize-package-bin": {
               "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",
+            "needle": {
+              "version": "2.3.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "wrappy": "1"
+                "debug": "^3.2.6",
+                "iconv-lite": "^0.4.4",
+                "sax": "^1.2.4"
               }
             },
-            "os-homedir": {
-              "version": "1.0.2",
+            "ini": {
+              "version": "1.3.5",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "os-tmpdir": {
-              "version": "1.0.2",
+            "minimatch": {
+              "version": "3.0.4",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "optional": true,
+              "requires": {
+                "brace-expansion": "^1.1.7"
+              }
             },
-            "osenv": {
-              "version": "0.1.5",
+            "mkdirp": {
+              "version": "0.5.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "os-homedir": "^1.0.0",
-                "os-tmpdir": "^1.0.0"
+                "minimist": "^1.2.5"
               }
             },
-            "path-is-absolute": {
-              "version": "1.0.1",
+            "set-blocking": {
+              "version": "2.0.0",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "process-nextick-args": {
-              "version": "2.0.1",
+            "wrappy": {
+              "version": "1.0.2",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "rc": {
-              "version": "1.2.8",
+            "fs-minipass": {
+              "version": "1.2.7",
               "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"
+                "minipass": "^2.6.0"
               }
             },
-            "readable-stream": {
-              "version": "2.3.7",
+            "core-util-is": {
+              "version": "1.0.2",
               "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"
-              }
+              "optional": true
+            },
+            "number-is-nan": {
+              "version": "1.0.1",
+              "bundled": true,
+              "optional": true
+            },
+            "isarray": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
             },
             "rimraf": {
               "version": "2.7.1",
               "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",
+            "minimist": {
+              "version": "1.2.5",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "semver": {
-              "version": "5.7.1",
+            "glob": {
+              "version": "7.1.6",
               "bundled": true,
-              "dev": true,
-              "optional": 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"
+              }
             },
-            "set-blocking": {
-              "version": "2.0.0",
+            "object-assign": {
+              "version": "4.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "signal-exit": {
-              "version": "3.0.2",
+            "delegates": {
+              "version": "1.0.0",
               "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",
+            "ignore-walk": {
+              "version": "3.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "safe-buffer": "~5.1.0"
+                "minimatch": "^3.0.4"
               }
             },
-            "strip-ansi": {
-              "version": "3.0.1",
+            "inflight": {
+              "version": "1.0.6",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "ansi-regex": "^2.0.0"
+                "once": "^1.3.0",
+                "wrappy": "1"
               }
             },
-            "strip-json-comments": {
-              "version": "2.0.1",
+            "path-is-absolute": {
+              "version": "1.0.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "tar": {
-              "version": "4.4.13",
+            "iconv-lite": {
+              "version": "0.4.24",
               "bundled": true,
-              "dev": true,
               "optional": 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"
+                "safer-buffer": ">= 2.1.2 < 3"
               }
             },
-            "util-deprecate": {
-              "version": "1.0.2",
+            "detect-libc": {
+              "version": "1.0.3",
               "bundled": true,
-              "dev": true,
               "optional": true
             },
-            "wide-align": {
-              "version": "1.1.3",
+            "string_decoder": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true,
               "requires": {
-                "string-width": "^1.0.2 || 2"
+                "safe-buffer": "~5.1.0"
               }
             },
-            "wrappy": {
-              "version": "1.0.2",
-              "bundled": true,
-              "dev": true,
-              "optional": true
-            },
-            "yallist": {
-              "version": "3.1.1",
+            "abbrev": {
+              "version": "1.1.1",
               "bundled": true,
-              "dev": true,
               "optional": true
             }
           },
           }
         },
         "mem": {
-          "dev": true,
           "requires": {
             "map-age-cleaner": "^0.1.1",
             "mimic-fn": "^2.0.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
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "os-locale": {
           "version": "3.1.0",
index 8aeece9c5d6dad9ba26706a962239d89efc0b088..3250965721aef185acfde861c7df27aae561e675 100644 (file)
     "@auth0/angular-jwt": "2.1.1",
     "@ngx-translate/i18n-polyfill": "1.0.0",
     "@swimlane/ngx-datatable": "16.0.3",
+    "@types/file-saver": "^2.0.1",
     "angular-tree-component": "8.5.6",
     "async-mutex": "0.1.4",
     "bootstrap": "4.4.1",
     "chart.js": "2.8.0",
     "detect-browser": "5.0.0",
+    "file-saver": "^2.0.2",
     "fork-awesome": "1.1.7",
     "lodash": "4.17.15",
     "moment": "2.24.0",
index cbdef74b61da2de6c4f0fe588368dc6bec4eebd3..f9f1b26220a873dbfd8cb19dde53685a4d867c47 100644 (file)
@@ -19,6 +19,7 @@ import { OsdListComponent } from './ceph/cluster/osd/osd-list/osd-list.component
 import { MonitoringListComponent } from './ceph/cluster/prometheus/monitoring-list/monitoring-list.component';
 import { SilenceFormComponent } from './ceph/cluster/prometheus/silence-form/silence-form.component';
 import { ServicesComponent } from './ceph/cluster/services/services.component';
+import { TelemetryComponent } from './ceph/cluster/telemetry/telemetry.component';
 import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.component';
 import { Nfs501Component } from './ceph/nfs/nfs-501/nfs-501.component';
 import { NfsFormComponent } from './ceph/nfs/nfs-form/nfs-form.component';
@@ -143,6 +144,11 @@ const routes: Routes = [
         component: LogsComponent,
         data: { breadcrumbs: 'Cluster/Logs' }
       },
+      {
+        path: 'telemetry',
+        component: TelemetryComponent,
+        data: { breadcrumbs: 'Telemetry configuration' }
+      },
       {
         path: 'monitoring',
         data: { breadcrumbs: 'Cluster/Monitoring' },
index e1634f1e4d17611ecad7fa73ac2e93b894823800..5eb59c9e7f54e8f83e533fed6bcc97a95d12bfb2 100644 (file)
@@ -51,6 +51,7 @@ import { SilenceMatcherModalComponent } from './prometheus/silence-matcher-modal
 import { ServiceDaemonListComponent } from './services/service-daemon-list/service-daemon-list.component';
 import { ServiceDetailsComponent } from './services/service-details/service-details.component';
 import { ServicesComponent } from './services/services.component';
+import { TelemetryComponent } from './telemetry/telemetry.component';
 
 @NgModule({
   entryComponents: [
@@ -122,7 +123,8 @@ import { ServicesComponent } from './services/services.component';
     MonitoringListComponent,
     HostFormComponent,
     ServiceDetailsComponent,
-    ServiceDaemonListComponent
+    ServiceDaemonListComponent,
+    TelemetryComponent
   ]
 })
 export class ClusterModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html
new file mode 100644 (file)
index 0000000..8d02a03
--- /dev/null
@@ -0,0 +1,309 @@
+<cd-loading-panel *ngIf="loading && !error"
+                  i18n>Loading configuration...</cd-loading-panel>
+<cd-alert-panel type="error"
+                *ngIf="loading && error"
+                i18n>The configuration could not be loaded.</cd-alert-panel>
+
+<div class="cd-col-form"
+     *ngIf="!loading && !error">
+  <ng-container [ngSwitch]="step">
+    <!-- Configuration step -->
+    <div *ngSwitchCase="1">
+      <form name="form"
+            #formDir="ngForm"
+            [formGroup]="configForm"
+            novalidate>
+        <div class="card">
+          <div class="card-header"
+               i18n>Step {{ step }} of 2: Telemetry report configuration</div>
+          <div class="card-body">
+            <p i18n>The telemetry module sends anonymous data about this Ceph cluster back to the Ceph developers
+              to help understand how Ceph is used and what problems users may be experiencing.<br/>
+              This data is visualized on <a href="https://telemetry-public.ceph.com/">public dashboards</a>
+              that allow the community to quickly see summary statistics on how many clusters are reporting,
+              their total capacity and OSD count, and version distribution trends.<br/><br/>
+              The data being reported does <b>not</b> contain any sensitive data like pool names, object names, object contents,
+              hostnames, or device serial numbers. It contains counters and statistics on how the cluster has been
+              deployed, the version of Ceph, the distribution of the hosts and other parameters which help the project
+              to gain a better understanding of the way Ceph is used. The data is sent secured to https://telemetry.ceph.com.</p>
+            <div *ngIf="moduleEnabled">
+              The plugin is already <b>enabled</b>. Click <b>Deactivate</b> to disable it.&nbsp;
+              <button type="button"
+                      class="btn btn-light"
+                      (click)="disableModule('The Telemetry module has been disabled successfully.')"
+                      i18n>Deactivate</button>
+            </div>
+            <legend i18n>Channels</legend>
+            <p i18n>The telemetry report is broken down into several "channels", each with a different type of information that can
+              be configured below.</p>
+
+            <!-- Channel basic -->
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="channel_basic">
+                <ng-container i18n>Basic</ng-container>
+                <cd-helper>
+                  <ng-container i18n>Includes basic information about the cluster:</ng-container>
+                  <ul>
+                    <li i18n>Capacity of the cluster</li>
+                    <li i18n>Number of monitors, managers, OSDs, MDSs, object gateways, or other daemons</li>
+                    <li i18n>Software version currently being used</li>
+                    <li i18n>Number and types of RADOS pools and CephFS file systems</li>
+                    <li i18n>Names of configuration options that have been changed from their default (but not their values)</li>
+                  </ul>
+                </cd-helper>
+              </label>
+              <div class="cd-col-form-input">
+                <div class="custom-control custom-checkbox">
+                  <input type="checkbox"
+                         class="custom-control-input"
+                         id="channel_basic"
+                         formControlName="channel_basic">
+                  <label class="custom-control-label"
+                         for="channel_basic"></label>
+                </div>
+              </div>
+            </div>
+
+            <!-- Channel crash -->
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="channel_crash">
+                <ng-container i18n>Crash</ng-container>
+                <cd-helper>
+                  <ng-container i18n>Includes information about daemon crashes:</ng-container>
+                  <ul>
+                    <li i18n>Type of daemon</li>
+                    <li i18n>Version of the daemon</li>
+                    <li i18n>Operating system (OS distribution, kernel version)</li>
+                    <li i18n>Stack trace identifying where in the Ceph code the crash occurred</li>
+                  </ul>
+                </cd-helper>
+              </label>
+              <div class="cd-col-form-input">
+                <div class="custom-control custom-checkbox">
+                  <input type="checkbox"
+                         class="custom-control-input"
+                         id="channel_crash"
+                         formControlName="channel_crash">
+                  <label class="custom-control-label"
+                         for="channel_crash"></label>
+                </div>
+              </div>
+            </div>
+
+            <!-- Channel device -->
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="channel_device">
+                <ng-container i18n>Device</ng-container>
+                <cd-helper i18n-html
+                           html="Includes information about device metrics like anonymized SMART metrics.">
+                </cd-helper>
+              </label>
+              <div class="cd-col-form-input">
+                <div class="custom-control custom-checkbox">
+                  <input type="checkbox"
+                         class="custom-control-input"
+                         id="channel_device"
+                         formControlName="channel_device">
+                  <label class="custom-control-label"
+                         for="channel_device"></label>
+                </div>
+              </div>
+            </div>
+
+            <!-- Channel ident -->
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="channel_ident">
+                <ng-container i18n>Ident</ng-container>
+                <cd-helper>
+                  <ng-container i18n>Includes user-provided identifying information about the cluster:</ng-container>
+                  <ul>
+                    <li>Cluster description</li>
+                    <li>Contact email address</li>
+                  </ul>
+                </cd-helper>
+              </label>
+              <div class="cd-col-form-input">
+                <div class="custom-control custom-checkbox">
+                  <input type="checkbox"
+                         class="custom-control-input"
+                         id="channel_ident"
+                         formControlName="channel_ident">
+                  <label class="custom-control-label"
+                         for="channel_ident"></label>
+                </div>
+              </div>
+            </div>
+            <legend>
+              <ng-container i18n>Contact Information</ng-container>
+              <cd-helper i18n>Submitting any contact information is completely optional and disabled by default.</cd-helper>
+            </legend>
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="contact"
+                     i18n>Contact</label>
+              <div class="cd-col-form-input">
+                <input id="contact"
+                       class="form-control"
+                       type="text"
+                       formControlName="contact"
+                       placeholder="Example User <user@example.com>">
+              </div>
+            </div>
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="description"
+                     i18n>Description</label>
+              <div class="cd-col-form-input">
+                <input id="description"
+                       class="form-control"
+                       type="text"
+                       formControlName="description"
+                       placeholder="My first Ceph cluster"
+                       i18n-placeholder>
+              </div>
+            </div>
+            <legend i18n>Advanced Settings</legend>
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="interval">
+                <ng-container i18n>Interval</ng-container>
+                <cd-helper i18n>The module compiles and sends a new report every 24 hours by default. You can
+                  adjust this interval by setting a different number of hours.</cd-helper>
+              </label>
+              <div class="cd-col-form-input">
+                <input id="interval"
+                       class="form-control"
+                       type="number"
+                       formControlName="interval">
+                <span class="invalid-feedback"
+                      *ngIf="configForm.showError('interval', formDir, 'min')"
+                      i18n>The entered value is too low! It must be greater or equal to 8.</span>
+              </div>
+            </div>
+            <div class="form-group row">
+              <label class="cd-col-form-label"
+                     for="proxy">
+                <ng-container i18n>Proxy</ng-container>
+                <cd-helper>
+                  <p i18n>If the cluster cannot directly connect to the configured telemetry endpoint
+                    (default telemetry.ceph.com), you can configure a HTTP/HTTPS proxy server by e.g. adding
+                    https://10.0.0.1:8080</p>
+                  <p i18n>You can also include a user:pass if needed e.g. https://ceph:telemetry@10.0.0.1:8080</p>
+                </cd-helper>
+              </label>
+              <div class="cd-col-form-input">
+                <input id="proxy"
+                       class="form-control"
+                       type="text"
+                       formControlName="proxy"
+                       placeholder="https://10.0.0.1:8080">
+              </div>
+            </div>
+            <br />
+            <p i18n><b>Note:</b> By clicking 'Next' you will first see a preview of the report content before you
+              can activate the automatic submission of your data.</p>
+          </div>
+          <div class="card-footer">
+            <div class="button-group text-right">
+              <button type="button"
+                      class="btn btn-light"
+                      (click)="next()">
+                <ng-container i18n>Next</ng-container>
+              </button>
+            </div>
+          </div>
+        </div>
+      </form>
+    </div>
+
+    <!-- Preview step -->
+    <div *ngSwitchCase="2">
+      <form name="previewForm"
+            #frm="ngForm"
+            [formGroup]="previewForm"
+            novalidate>
+        <div class="card">
+          <div class="card-header"
+               i18n>Step {{ step }} of 2: Telemetry report preview</div>
+          <div class="card-body">
+            <!-- Telemetry report ID -->
+            <div class="form-group row">
+              <label i18n
+                     for="reportId"
+                     class="cd-col-form-label">Report ID</label>
+              <div class="cd-col-form-input">
+                <input class="form-control"
+                       type="text"
+                       id="reportId"
+                       formControlName="reportId"
+                       readonly>
+              </div>
+            </div>
+
+            <!-- Telemetry report -->
+            <div class="form-group row">
+              <label i18n
+                     for="report"
+                     class="cd-col-form-label">Report preview</label>
+              <div class="cd-col-form-input">
+                <textarea class="form-control"
+                          id="report"
+                          formControlName="report"
+                          rows="15"
+                          readonly></textarea>
+              </div>
+            </div>
+            <div class="form-group row">
+              <div class="cd-col-form-offset">
+                <button type="button"
+                        class="btn btn-light mr-2"
+                        title="Download"
+                        (click)="download(report, 'telemetry_report.json')"
+                        i18n-title>
+                  <i class="fa fa-download"></i>
+                </button>
+                <button type="button"
+                        class="btn btn-light"
+                        cdCopy2ClipboardButton="report"
+                        formatted>
+                </button>
+              </div>
+            </div>
+
+            <!-- License agreement -->
+            <div class="form-group row">
+              <div class="cd-col-form-offset">
+                <div class="custom-control custom-checkbox">
+                  <input type="checkbox"
+                         class="custom-control-input"
+                         id="licenseAgrmt"
+                         name="licenseAgrmt"
+                         formControlName="licenseAgrmt">
+                  <label class="custom-control-label"
+                         for="licenseAgrmt"
+                         i18n>I agree to my telemetry data being submitted under the Community Data License Agreement - Sharing - Version 1.0 (<a href="https://cdla.io/sharing-1-0/">https://cdla.io/sharing-1-0/</a>)</label>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="card-footer">
+            <div class="button-group text-right">
+              <cd-submit-button (submitAction)="onSubmit()"
+                                [form]="previewForm">
+                <ng-container i18n>Save</ng-container>
+              </cd-submit-button>
+              <button type="button"
+                      class="btn btn-light"
+                      (click)="back()"
+                      i18n>Back</button>
+            </div>
+          </div>
+        </div>
+      </form>
+    </div>
+  </ng-container>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts
new file mode 100644 (file)
index 0000000..783eeed
--- /dev/null
@@ -0,0 +1,180 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import * as _ from 'lodash';
+import { ToastrModule } from 'ngx-toastr';
+import { of as observableOf } from 'rxjs';
+
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { MgrModuleService } from '../../../shared/api/mgr-module.service';
+import { TelemetryService } from '../../../shared/api/telemetry.service';
+
+import { TextToDownloadService } from '../../../shared/services/text-to-download.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { TelemetryComponent } from './telemetry.component';
+
+describe('TelemetryComponent', () => {
+  let component: TelemetryComponent;
+  let fixture: ComponentFixture<TelemetryComponent>;
+  let mgrModuleService: MgrModuleService;
+  let telemetryService: TelemetryService;
+  let options: any;
+  let configs: any;
+  let httpTesting: HttpTestingController;
+  let router: Router;
+
+  const optionsNames = [
+    'channel_basic',
+    'channel_crash',
+    'channel_device',
+    'channel_ident',
+    'contact',
+    'description',
+    'device_url',
+    'enabled',
+    'interval',
+    'last_opt_revision',
+    'leaderboard',
+    'log_level',
+    'log_to_cluster',
+    'log_to_cluster_level',
+    'log_to_file',
+    'organization',
+    'proxy',
+    'url'
+  ];
+
+  configureTestBed(
+    {
+      declarations: [TelemetryComponent],
+      imports: [
+        HttpClientTestingModule,
+        ReactiveFormsModule,
+        RouterTestingModule,
+        SharedModule,
+        ToastrModule.forRoot()
+      ],
+      providers: i18nProviders
+    },
+    true
+  );
+
+  describe('configForm', () => {
+    beforeEach(() => {
+      fixture = TestBed.createComponent(TelemetryComponent);
+      component = fixture.componentInstance;
+      mgrModuleService = TestBed.get(MgrModuleService);
+      options = {};
+      configs = {};
+      optionsNames.forEach((name) => (options[name] = { name }));
+      optionsNames.forEach((name) => (configs[name] = true));
+      spyOn(mgrModuleService, 'getOptions').and.callFake(() => observableOf(options));
+      spyOn(mgrModuleService, 'getConfig').and.callFake(() => observableOf(configs));
+      fixture.detectChanges();
+      httpTesting = TestBed.get(HttpTestingController);
+      router = TestBed.get(Router);
+      spyOn(router, 'navigate');
+    });
+
+    it('should create', () => {
+      expect(component).toBeTruthy();
+    });
+
+    it('should set module enability to true correctly', () => {
+      expect(component.moduleEnabled).toBeTruthy();
+    });
+
+    it('should set module enability to false correctly', () => {
+      configs['enabled'] = false;
+      component.ngOnInit();
+      expect(component.moduleEnabled).toBeFalsy();
+    });
+
+    it('should filter options list correctly', () => {
+      _.forEach(Object.keys(component.options), (option) => {
+        expect(component.requiredFields).toContain(option);
+      });
+    });
+
+    it('should update the Telemetry configuration', () => {
+      component.updateConfig();
+      const req = httpTesting.expectOne('api/mgr/module/telemetry');
+      expect(req.request.method).toBe('PUT');
+      expect(req.request.body).toEqual({
+        config: {}
+      });
+      req.flush({});
+    });
+
+    it('should disable the Telemetry module', () => {
+      const message = 'Module disabled message.';
+      const followUpFunc = function () {
+        return 'followUp';
+      };
+      component.disableModule(message, followUpFunc);
+      const req = httpTesting.expectOne('api/telemetry');
+      expect(req.request.method).toBe('PUT');
+      expect(req.request.body).toEqual({
+        enable: false
+      });
+      req.flush({});
+    });
+
+    it('should disable the Telemetry module with default parameters', () => {
+      component.disableModule();
+      const req = httpTesting.expectOne('api/telemetry');
+      expect(req.request.method).toBe('PUT');
+      expect(req.request.body).toEqual({
+        enable: false
+      });
+      req.flush({});
+      expect(router.navigate).toHaveBeenCalledWith(['']);
+    });
+  });
+
+  describe('previewForm', () => {
+    const reportText = {
+      testA: 'testA',
+      testB: 'testB'
+    };
+
+    beforeEach(() => {
+      fixture = TestBed.createComponent(TelemetryComponent);
+      component = fixture.componentInstance;
+      fixture.detectChanges();
+      telemetryService = TestBed.get(TelemetryService);
+      httpTesting = TestBed.get(HttpTestingController);
+      router = TestBed.get(Router);
+      spyOn(router, 'navigate');
+    });
+
+    it('should create', () => {
+      expect(component).toBeTruthy();
+    });
+
+    it('should call TextToDownloadService download function', () => {
+      spyOn(telemetryService, 'getReport').and.returnValue(observableOf(reportText));
+      component.ngOnInit();
+
+      const downloadSpy = spyOn(TestBed.get(TextToDownloadService), 'download');
+      const filename = 'reportText.json';
+      component.download(reportText, filename);
+      expect(downloadSpy).toHaveBeenCalledWith(JSON.stringify(reportText, null, 2), filename);
+    });
+
+    it('should submit', () => {
+      component.onSubmit();
+      const req = httpTesting.expectOne('api/telemetry');
+      expect(req.request.method).toBe('PUT');
+      expect(req.request.body).toEqual({
+        enable: true,
+        license_name: 'sharing-1-0'
+      });
+      req.flush({});
+      expect(router.navigate).toHaveBeenCalledWith(['']);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts
new file mode 100644 (file)
index 0000000..b258d2c
--- /dev/null
@@ -0,0 +1,203 @@
+import { Component, OnInit } from '@angular/core';
+import { ValidatorFn, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+import { BlockUI, NgBlockUI } from 'ng-block-ui';
+import { forkJoin as observableForkJoin } from 'rxjs';
+
+import { MgrModuleService } from '../../../shared/api/mgr-module.service';
+import { TelemetryService } from '../../../shared/api/telemetry.service';
+import { NotificationType } from '../../../shared/enum/notification-type.enum';
+import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
+import { CdFormGroup } from '../../../shared/forms/cd-form-group';
+import { CdValidators } from '../../../shared/forms/cd-validators';
+import { NotificationService } from '../../../shared/services/notification.service';
+import { TextToDownloadService } from '../../../shared/services/text-to-download.service';
+
+@Component({
+  selector: 'cd-telemetry',
+  templateUrl: './telemetry.component.html',
+  styleUrls: ['./telemetry.component.scss']
+})
+export class TelemetryComponent implements OnInit {
+  @BlockUI()
+  blockUI: NgBlockUI;
+
+  error = false;
+  configForm: CdFormGroup;
+  licenseAgrmt = false;
+  loading = false;
+  moduleEnabled: boolean;
+  options: Object = {};
+  previewForm: CdFormGroup;
+  requiredFields = [
+    'channel_basic',
+    'channel_crash',
+    'channel_device',
+    'channel_ident',
+    'interval',
+    'proxy',
+    'contact',
+    'description'
+  ];
+  report: object = undefined;
+  reportId: number = undefined;
+  step = 1;
+
+  constructor(
+    private formBuilder: CdFormBuilder,
+    private mgrModuleService: MgrModuleService,
+    private notificationService: NotificationService,
+    private router: Router,
+    private telemetryService: TelemetryService,
+    private i18n: I18n,
+    private textToDownloadService: TextToDownloadService
+  ) {}
+
+  ngOnInit() {
+    this.loading = true;
+    const observables = [
+      this.mgrModuleService.getOptions('telemetry'),
+      this.mgrModuleService.getConfig('telemetry')
+    ];
+    observableForkJoin(observables).subscribe(
+      (resp: object) => {
+        this.moduleEnabled = resp[1]['enabled'];
+        this.options = _.pick(resp[0], this.requiredFields);
+        const configs = _.pick(resp[1], this.requiredFields);
+        this.createConfigForm();
+        this.configForm.setValue(configs);
+        this.loading = false;
+      },
+      (_error) => {
+        this.error = true;
+      }
+    );
+  }
+
+  private createConfigForm() {
+    const controlsConfig = {};
+    _.forEach(Object.values(this.options), (option) => {
+      controlsConfig[option.name] = [option.default_value, this.getValidators(option)];
+    });
+    this.configForm = this.formBuilder.group(controlsConfig);
+  }
+
+  private createPreviewForm() {
+    const controls = {
+      report: JSON.stringify(this.report, null, 2),
+      reportId: this.reportId,
+      licenseAgrmt: [this.licenseAgrmt, Validators.requiredTrue]
+    };
+    this.previewForm = this.formBuilder.group(controls);
+  }
+
+  private getValidators(option: any): ValidatorFn[] {
+    const result = [];
+    switch (option.type) {
+      case 'int':
+        result.push(CdValidators.number());
+        result.push(Validators.required);
+        if (_.isNumber(option.min)) {
+          result.push(Validators.min(option.min));
+        }
+        if (_.isNumber(option.max)) {
+          result.push(Validators.max(option.max));
+        }
+        break;
+      case 'str':
+        if (_.isNumber(option.min)) {
+          result.push(Validators.minLength(option.min));
+        }
+        if (_.isNumber(option.max)) {
+          result.push(Validators.maxLength(option.max));
+        }
+        break;
+    }
+    return result;
+  }
+
+  private getReport() {
+    this.loading = true;
+    this.telemetryService.getReport().subscribe(
+      (resp: object) => {
+        this.report = resp;
+        this.reportId = resp['report']['report_id'];
+        this.createPreviewForm();
+        this.loading = false;
+        this.step++;
+      },
+      (_error) => {
+        this.error = true;
+      }
+    );
+  }
+
+  updateConfig() {
+    const config = {};
+    _.forEach(Object.values(this.options), (option) => {
+      const control = this.configForm.get(option.name);
+      // Append the option only if the value has been modified.
+      if (control.dirty && control.valid) {
+        config[option.name] = control.value;
+      }
+    });
+    this.mgrModuleService.updateConfig('telemetry', config).subscribe(
+      () => {
+        this.disableModule(
+          'Your settings have been applied successfully. ' +
+            'Due to privacy/legal reasons the Telemetry module is now disabled until you ' +
+            'complete the next step and accept the license.',
+          () => {
+            this.getReport();
+          }
+        );
+      },
+      () => {
+        // Reset the 'Submit' button.
+        this.configForm.setErrors({ cdSubmitButton: true });
+      }
+    );
+  }
+
+  download(report: object, fileName: string) {
+    this.textToDownloadService.download(JSON.stringify(report, null, 2), fileName);
+  }
+
+  disableModule(message: string = null, followUpFunc: Function = null) {
+    this.telemetryService.enable(false).subscribe(() => {
+      if (message) {
+        this.notificationService.show(NotificationType.success, this.i18n(message));
+      }
+      if (followUpFunc) {
+        followUpFunc();
+      } else {
+        this.router.navigate(['']);
+      }
+    });
+  }
+
+  next() {
+    if (this.configForm.pristine) {
+      this.getReport();
+    } else {
+      this.updateConfig();
+    }
+  }
+
+  back() {
+    this.step--;
+  }
+
+  onSubmit() {
+    this.telemetryService.enable().subscribe(() => {
+      this.notificationService.show(
+        NotificationType.success,
+        this.i18n('The Telemetry module has been configured and activated successfully.')
+      );
+      this.router.navigate(['']);
+    });
+  }
+}
index 6a64276137b05e8f9f1942e0cd2124a4f1b66410..52d045ca645f8bcbca2310dea8e1a27e19d35626 100644 (file)
          class="dropdown-item"
          routerLink="/user-management">User management</a>
     </li>
+    <li *ngIf="configOptPermission.read">
+      <a i18n
+         class="dropdown-item"
+         routerLink="/telemetry">Telemetry configuration</a>
+    </li>
   </ul>
 </div>
index ccb6ec80f2155197d8cd2732335fda1c073da486..4d08bbe06eb7ac2afc557fbc8cd5851f180b1acf 100644 (file)
@@ -11,10 +11,13 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic
 })
 export class AdministrationComponent implements OnInit {
   userPermission: Permission;
+  configOptPermission: Permission;
   icons = Icons;
 
   constructor(private authStorageService: AuthStorageService) {
-    this.userPermission = this.authStorageService.getPermissions().user;
+    const permissions = this.authStorageService.getPermissions();
+    this.userPermission = permissions.user;
+    this.configOptPermission = permissions.configOpt;
   }
 
   ngOnInit() {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.spec.ts
new file mode 100644 (file)
index 0000000..d4d02a9
--- /dev/null
@@ -0,0 +1,58 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+import { TelemetryService } from './telemetry.service';
+
+describe('TelemetryService', () => {
+  let service: TelemetryService;
+  let httpTesting: HttpTestingController;
+
+  configureTestBed({
+    imports: [HttpClientTestingModule],
+    providers: [TelemetryService]
+  });
+
+  beforeEach(() => {
+    service = TestBed.get(TelemetryService);
+    httpTesting = TestBed.get(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpTesting.verify();
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should call getReport', () => {
+    service.getReport().subscribe();
+    const req = httpTesting.expectOne('api/telemetry/report');
+    expect(req.request.method).toBe('GET');
+  });
+
+  it('should call enable to enable module', () => {
+    service.enable(true).subscribe();
+    const req = httpTesting.expectOne('api/telemetry');
+    expect(req.request.method).toBe('PUT');
+    expect(req.request.body.enable).toBe(true);
+    expect(req.request.body.license_name).toBe('sharing-1-0');
+  });
+
+  it('should call enable to disable module', () => {
+    service.enable(false).subscribe();
+    const req = httpTesting.expectOne('api/telemetry');
+    expect(req.request.method).toBe('PUT');
+    expect(req.request.body.enable).toBe(false);
+    expect(req.request.body.license_name).toBeUndefined();
+  });
+
+  it('should call enable to enable module by default', () => {
+    service.enable().subscribe();
+    const req = httpTesting.expectOne('api/telemetry');
+    expect(req.request.method).toBe('PUT');
+    expect(req.request.body.enable).toBe(true);
+    expect(req.request.body.license_name).toBe('sharing-1-0');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.ts
new file mode 100644 (file)
index 0000000..60365c1
--- /dev/null
@@ -0,0 +1,25 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+import { ApiModule } from './api.module';
+
+@Injectable({
+  providedIn: ApiModule
+})
+export class TelemetryService {
+  private url = 'api/telemetry';
+
+  constructor(private http: HttpClient) {}
+
+  getReport() {
+    return this.http.get(`${this.url}/report`);
+  }
+
+  enable(enable: boolean = true) {
+    const body = { enable: enable };
+    if (enable) {
+      body['license_name'] = 'sharing-1-0';
+    }
+    return this.http.put(`${this.url}`, body);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.spec.ts
new file mode 100644 (file)
index 0000000..871c1af
--- /dev/null
@@ -0,0 +1,20 @@
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+import { TextToDownloadService } from './text-to-download.service';
+
+describe('TextToDownloadService', () => {
+  let service: TextToDownloadService;
+
+  configureTestBed({
+    providers: [TextToDownloadService]
+  });
+
+  beforeEach(() => {
+    service = TestBed.get(TextToDownloadService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.ts
new file mode 100644 (file)
index 0000000..bcfb3ab
--- /dev/null
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+
+import { saveAs } from 'file-saver';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TextToDownloadService {
+  constructor() {}
+
+  download(downloadText: string, filename?: string) {
+    saveAs(new Blob([downloadText]), filename);
+  }
+}