From: Tatjana Dehler Date: Fri, 21 Feb 2020 09:08:10 +0000 (+0100) Subject: mgr/dashboard: add telemetry report component X-Git-Tag: wip-pdonnell-testing-20200918.022351~1406^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=18875ef337036696914a9056c666db66882cbe22;p=ceph-ci.git mgr/dashboard: add telemetry report component 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 --- diff --git a/doc/mgr/telemetry.rst b/doc/mgr/telemetry.rst index 64c2d0f0e5b..6eaaa5c4492 100644 --- a/doc/mgr/telemetry.rst +++ b/doc/mgr/telemetry.rst @@ -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 ------------- diff --git a/qa/suites/rados/dashboard/tasks/dashboard.yaml b/qa/suites/rados/dashboard/tasks/dashboard.yaml index e61294eeed9..f210fc1c86d 100644 --- a/qa/suites/rados/dashboard/tasks/dashboard.yaml +++ b/qa/suites/rados/dashboard/tasks/dashboard.yaml @@ -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 index 00000000000..840ccddca81 --- /dev/null +++ b/qa/tasks/mgr/dashboard/test_telemetry.py @@ -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) diff --git a/src/pybind/mgr/dashboard/controllers/__init__.py b/src/pybind/mgr/dashboard/controllers/__init__.py index 3bd4cba8d73..a72ed73ce0a 100644 --- a/src/pybind/mgr/dashboard/controllers/__init__.py +++ b/src/pybind/mgr/dashboard/controllers/__init__.py @@ -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 index 00000000000..c68432ece17 --- /dev/null +++ b/src/pybind/mgr/dashboard/controllers/telemetry.py @@ -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') diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 139d7887769..85556b75569 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -479,326 +479,302 @@ } }, "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", @@ -806,220 +782,172 @@ "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 } }, @@ -1099,7 +1027,6 @@ } }, "mem": { - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -1110,8 +1037,7 @@ "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", @@ -3412,6 +3338,11 @@ "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", @@ -7467,6 +7398,11 @@ } } }, + "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", @@ -7828,11 +7764,6 @@ "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", @@ -8337,7 +8268,6 @@ } }, "mem": { - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -8348,8 +8278,7 @@ "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", @@ -11676,326 +11605,302 @@ } }, "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", @@ -12003,220 +11908,172 @@ "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 } }, @@ -12633,7 +12490,6 @@ "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" } @@ -12682,12 +12538,6 @@ "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", @@ -12884,7 +12734,8 @@ "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", @@ -13786,6 +13637,11 @@ "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==" } } }, @@ -13824,8 +13680,7 @@ "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", @@ -13841,8 +13696,7 @@ "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", @@ -18320,284 +18174,199 @@ } }, "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", @@ -18611,256 +18380,269 @@ "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 } }, @@ -19222,326 +19004,302 @@ } }, "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", @@ -19549,220 +19307,172 @@ "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 } }, @@ -19830,7 +19540,6 @@ } }, "mem": { - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -19841,8 +19550,7 @@ "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", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index 8aeece9c5d6..3250965721a 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -89,11 +89,13 @@ "@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", diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index cbdef74b61d..f9f1b26220a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -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' }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index e1634f1e4d1..5eb59c9e7f5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -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 index 00000000000..8d02a031a24 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.html @@ -0,0 +1,309 @@ +Loading configuration... +The configuration could not be loaded. + +
+ + +
+
+
+
Step {{ step }} of 2: Telemetry report configuration
+
+

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.
+ This data is visualized on public dashboards + 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.

+ The data being reported does not 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.

+
+ The plugin is already enabled. Click Deactivate to disable it.  + +
+ Channels +

The telemetry report is broken down into several "channels", each with a different type of information that can + be configured below.

+ + +
+ +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+
+ + Contact Information + Submitting any contact information is completely optional and disabled by default. + +
+ +
+ +
+
+
+ +
+ +
+
+ Advanced Settings +
+ +
+ + The entered value is too low! It must be greater or equal to 8. +
+
+
+ +
+ +
+
+
+

Note: By clicking 'Next' you will first see a preview of the report content before you + can activate the automatic submission of your data.

+
+ +
+
+
+ + +
+
+
+
Step {{ step }} of 2: Telemetry report preview
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ +
+
+
+
+
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 index 00000000000..e69de29bb2d 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 index 00000000000..783eeed810d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.spec.ts @@ -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; + 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 index 00000000000..b258d2c1869 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/telemetry/telemetry.component.ts @@ -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(['']); + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html index 6a64276137b..52d045ca645 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.html @@ -17,5 +17,10 @@ class="dropdown-item" routerLink="/user-management">User management +
  • + Telemetry configuration +
  • diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.ts index ccb6ec80f21..4d08bbe06eb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/administration/administration.component.ts @@ -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 index 00000000000..d4d02a9bc5f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.spec.ts @@ -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 index 00000000000..60365c1494b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/telemetry.service.ts @@ -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 index 00000000000..871c1afa303 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.spec.ts @@ -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 index 00000000000..bcfb3abaaf0 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/text-to-download.service.ts @@ -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); + } +}