diff --git a/app/.editorconfig b/app/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/app/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..674c6cf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/dist-server +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +server/node_modules/ diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..cab2aa8 --- /dev/null +++ b/app/README.md @@ -0,0 +1 @@ +https://medium.com/bb-tutorials-and-thoughts/how-to-develop-and-build-angular-app-with-nodejs-e24c40444421 \ No newline at end of file diff --git a/app/angular.json b/app/angular.json new file mode 100644 index 0000000..c68a2c9 --- /dev/null +++ b/app/angular.json @@ -0,0 +1,196 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": + { + "manta": + { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": + { + }, + "architect": + { + "build": + { + "builder": "@angular-devkit/build-angular:browser", + "options": + { + "progress": false, + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": + [ + "src/assets", + "src/manifest.webmanifest", + "src/env.js" + ], + "styles": + [ + "./node_modules/bootstrap/dist/css/bootstrap.min.css", + "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", + "./node_modules/ngx-toastr/toastr.css", + "src/styles/styles.scss", + "src/styles/icons.scss" + ], + "scripts": + [ + ] + }, + "configurations": + { + "production": + { + "fileReplacements": + [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": + { + "builder": "@angular-devkit/build-angular:dev-server", + "options": + { + "browserTarget": "manta:build", + "proxyConfig": "proxy.conf.js" + }, + "configurations": + { + "production": + { + "browserTarget": "manta:build:production" + } + } + }, + "extract-i18n": + { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": + { + "browserTarget": "manta:build" + } + }, + "test": + { + "builder": "@angular-devkit/build-angular:karma", + "options": + { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": + [ + "src/styles.css" + ], + "scripts": + [ + ], + "assets": + [ + "src/assets" + ] + } + }, + "lint": + { + "builder": "@angular-devkit/build-angular:tslint", + "options": + { + "tsConfig": + [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": + [ + "**/node_modules/**" + ] + } + }, + "server": + { + "builder": "@angular-devkit/build-angular:server", + "options": + { + "outputPath": "dist-server", + "main": "src/main.ts", + "tsConfig": "src/tsconfig.server.json" + }, + "configurations": + { + "dev": + { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": true + }, + "production": + { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false + } + } + } + } + }, + "manta-e2e": + { + "root": "e2e/", + "projectType": "application", + "architect": + { + "e2e": + { + "builder": "@angular-devkit/build-angular:protractor", + "options": + { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "manta:serve" + } + }, + "lint": + { + "builder": "@angular-devkit/build-angular:tslint", + "options": + { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": + [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "manta" +} diff --git a/app/browserslist b/app/browserslist new file mode 100644 index 0000000..8e09ab4 --- /dev/null +++ b/app/browserslist @@ -0,0 +1,9 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# For IE 9-11 support, please uncomment the last line of the file and adjust as needed +> 0.5% +last 2 versions +Firefox ESR +not dead +# IE 9-11 \ No newline at end of file diff --git a/app/e2e/protractor.conf.js b/app/e2e/protractor.conf.js new file mode 100644 index 0000000..d60eff0 --- /dev/null +++ b/app/e2e/protractor.conf.js @@ -0,0 +1,28 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require("jasmine-spec-reporter"); + +exports.config = { + allScriptsTimeout: 11000, + specs: ["./src/**/*.e2e-spec.ts"], + capabilities: { + browserName: "chrome" + }, + directConnect: true, + baseUrl: "http://localhost:4200/", + framework: "jasmine", + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require("ts-node").register({ + project: require("path").join(__dirname, "./tsconfig.e2e.json") + }); + jasmine + .getEnv() + .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/app/e2e/src/app.e2e-spec.ts b/app/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000..5b3b4b2 --- /dev/null +++ b/app/e2e/src/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { AppPage } from './app.po'; + +describe('App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getMainHeading()).toEqual('Hello, world!'); + }); +}); diff --git a/app/e2e/src/app.po.ts b/app/e2e/src/app.po.ts new file mode 100644 index 0000000..24bc8b3 --- /dev/null +++ b/app/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getMainHeading() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/app/e2e/tsconfig.e2e.json b/app/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..a6dd622 --- /dev/null +++ b/app/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/app/ngsw-config.json b/app/ngsw-config.json new file mode 100644 index 0000000..23720c3 --- /dev/null +++ b/app/ngsw-config.json @@ -0,0 +1,29 @@ +{ + "$schema": "./node_modules/@angular/service-worker/config/schema.json", + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "resources": { + "files": [ + "/favicon.ico", + "/index.html", + "/*.css", + "/*.js", + "/manifest.webmanifest" + ] + } + }, { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": [ + "/assets/**", + "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" + ] + } + } + ] +} diff --git a/app/package-lock.json b/app/package-lock.json new file mode 100644 index 0000000..60392e2 --- /dev/null +++ b/app/package-lock.json @@ -0,0 +1,16038 @@ +{ + "name": "manta", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1100.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1100.5.tgz", + "integrity": "sha512-yOYfucNouc1doTbcGbCNMXGMSc36+j97XpdNoeGyzFQ7GwezLAro0a9gxc5PdOxndfelkND7J1JuOjxdW5O17A==", + "dev": true, + "requires": { + "@angular-devkit/core": "11.0.5", + "rxjs": "6.6.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-devkit/build-angular": { + "version": "0.1100.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.1100.5.tgz", + "integrity": "sha512-lJYsnBImBAqUAIVC2qGY64UaC2uWOPZEpSWjYUxkRZA/c4IVCJj3M12CgONBjtcKYzFVXc1eojhrScukGIJJcg==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1100.5", + "@angular-devkit/build-optimizer": "0.1100.5", + "@angular-devkit/build-webpack": "0.1100.5", + "@angular-devkit/core": "11.0.5", + "@babel/core": "7.12.3", + "@babel/generator": "7.12.1", + "@babel/plugin-transform-runtime": "7.12.1", + "@babel/preset-env": "7.12.1", + "@babel/runtime": "7.12.1", + "@babel/template": "7.10.4", + "@jsdevtools/coverage-istanbul-loader": "3.0.5", + "@ngtools/webpack": "11.0.5", + "ansi-colors": "4.1.1", + "autoprefixer": "9.8.6", + "babel-loader": "8.1.0", + "browserslist": "^4.9.1", + "cacache": "15.0.5", + "caniuse-lite": "^1.0.30001032", + "circular-dependency-plugin": "5.2.0", + "copy-webpack-plugin": "6.2.1", + "core-js": "3.6.5", + "css-loader": "4.3.0", + "cssnano": "4.1.10", + "file-loader": "6.1.1", + "find-cache-dir": "3.3.1", + "glob": "7.1.6", + "inquirer": "7.3.3", + "jest-worker": "26.5.0", + "karma-source-map-support": "1.4.0", + "less": "3.12.2", + "less-loader": "7.0.2", + "license-webpack-plugin": "2.3.1", + "loader-utils": "2.0.0", + "mini-css-extract-plugin": "1.2.1", + "minimatch": "3.0.4", + "open": "7.3.0", + "ora": "5.1.0", + "parse5-html-rewriting-stream": "6.0.1", + "pnp-webpack-plugin": "1.6.4", + "postcss": "7.0.32", + "postcss-import": "12.0.1", + "postcss-loader": "4.0.4", + "raw-loader": "4.0.2", + "regenerator-runtime": "0.13.7", + "resolve-url-loader": "3.1.2", + "rimraf": "3.0.2", + "rollup": "2.32.1", + "rxjs": "6.6.3", + "sass": "1.27.0", + "sass-loader": "10.0.5", + "semver": "7.3.2", + "source-map": "0.7.3", + "source-map-loader": "1.1.2", + "source-map-support": "0.5.19", + "speed-measure-webpack-plugin": "1.3.3", + "style-loader": "2.0.0", + "stylus": "0.54.8", + "stylus-loader": "4.3.1", + "terser": "5.3.7", + "terser-webpack-plugin": "4.2.3", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "webpack": "4.44.2", + "webpack-dev-middleware": "3.7.2", + "webpack-dev-server": "3.11.0", + "webpack-merge": "5.2.0", + "webpack-sources": "2.0.1", + "webpack-subresource-integrity": "1.5.1", + "worker-plugin": "5.0.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", + "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ora": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz", + "integrity": "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.4.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "mute-stream": "0.0.8", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@angular-devkit/build-optimizer": { + "version": "0.1100.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1100.5.tgz", + "integrity": "sha512-aKITFuiydR681eS1z84EIdOtqdxP/V5xGZuF3xjGmg5Ddwv36PweAHaCVJEB4btHSWH6uxMvW2hLXg2RTWbRNg==", + "dev": true, + "requires": { + "loader-utils": "2.0.0", + "source-map": "0.7.3", + "tslib": "2.0.3", + "typescript": "4.0.5", + "webpack-sources": "2.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + }, + "typescript": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1100.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1100.5.tgz", + "integrity": "sha512-oD5t2oCfyiCyyeZckrqBnQco94zIMkRnRGzy3lFDH7KMiL0DG9l7x3nxn9H0YunYWr55LsGWwXGoR7l03Kl+jw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1100.5", + "@angular-devkit/core": "11.0.5", + "rxjs": "6.6.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-devkit/core": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-11.0.5.tgz", + "integrity": "sha512-hwV8fjF8JNPJkiVWw8MNzeIfDo01aD/OAOlC4L5rQnVHn+i2EiU3brSDmFqyeHPPV3h/QjuBkS3tkN7gSnVWaQ==", + "dev": true, + "requires": { + "ajv": "6.12.6", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.3", + "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "@angular-devkit/schematics": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-11.0.5.tgz", + "integrity": "sha512-0NKGC8Nf/4vvDpWKB7bwxIazvNnNHnZBX6XlyBXNl+fW8tpTef3PNMJMSErTz9LFnuv61vsKbc36u/Ek2YChWg==", + "dev": true, + "requires": { + "@angular-devkit/core": "11.0.5", + "ora": "5.1.0", + "rxjs": "6.6.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-slider/ngx-slider": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@angular-slider/ngx-slider/-/ngx-slider-2.0.3.tgz", + "integrity": "sha512-5NSHsYtHomBgJyPe7KtxTAJDLywHbuTb36NjD3dafbbj1VUbshX1m04d4JcyEiAB+Zeetcjkiy4jxQypUXYhHA==", + "requires": { + "detect-passive-events": "^1.0.4", + "rxjs": "^6.5.2", + "rxjs-compat": "^6.5.2", + "tslib": "^1.9.0" + } + }, + "@angular/animations": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.0.5.tgz", + "integrity": "sha512-ghE/xDTYuEWkKNZtioH9JBrSlux0MLHzWoE7tNP+XMaplt80lCm979vWsEBO3/xpQLRmRlGPul6RacCAoeqogg==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/cdk": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.0.3.tgz", + "integrity": "sha512-hgbJXvZURKBnZawwxUrsZE/3a+HCJh2UhoLIng3cn5Q+WIW/4a37knDl8B9DYKBWrCqeINXNcUHVSKkWc/gjCA==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/cli": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-11.0.5.tgz", + "integrity": "sha512-k4j/2z7qkuigJ1shH0McW1wW63clhrbrg98FK4/KWhU/sce5AgVjuHDQFycAclTwHesf7Vs6Gzt7zGlqUmeKIg==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1100.5", + "@angular-devkit/core": "11.0.5", + "@angular-devkit/schematics": "11.0.5", + "@schematics/angular": "11.0.5", + "@schematics/update": "0.1100.5", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.1", + "debug": "4.2.0", + "ini": "1.3.6", + "inquirer": "7.3.3", + "npm-package-arg": "8.1.0", + "npm-pick-manifest": "6.1.0", + "open": "7.3.0", + "pacote": "9.5.12", + "resolve": "1.18.1", + "rimraf": "3.0.2", + "semver": "7.3.2", + "symbol-observable": "2.0.3", + "universal-analytics": "0.4.23", + "uuid": "8.3.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ini": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.6.tgz", + "integrity": "sha512-IZUoxEjNjubzrmvzZU4lKP7OnYmX72XRl3sqkfJhBKweKi5rnGi5+IUdlj/H1M+Ip5JQ1WzaDMOBRY90Ajc5jg==", + "dev": true + }, + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "dev": true, + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", + "dev": true + } + } + }, + "@angular/common": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.0.5.tgz", + "integrity": "sha512-aoXdTkoni65LWhrPKNsAiOnO70XFaTaisO+K8ZYMpciMTTAxHx3hFCF9sj4a+Bo3M1a5UDjpsFDYMeGgJOkmFA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/compiler": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.0.5.tgz", + "integrity": "sha512-japxEn07P9z9FnW8ii+M5DIfgRAGNxl6QNQWKBkNo5ytN6iCAB7pVbJI0vn1AUT9TByV3+xDW/FNuoSuzsnX3w==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/compiler-cli": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-11.0.5.tgz", + "integrity": "sha512-1EbnDdK2Em9xpnbLCjw+9w2F0I6gl5AS6QAn03ztYX9ZooNzCeC6sT8qghzrNTFTV89nyIoAqyMtgcLS6udVkg==", + "dev": true, + "requires": { + "@babel/core": "^7.8.6", + "@babel/types": "^7.8.6", + "canonical-path": "1.0.0", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "fs-extra": "4.0.2", + "magic-string": "^0.25.0", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "semver": "^6.3.0", + "source-map": "^0.6.1", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.0.0", + "yargs": "^16.1.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fs-extra": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", + "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, + "@angular/core": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.0.5.tgz", + "integrity": "sha512-XAXWQi7R3ucZXQwx9QK5jSKJeQyRJ53u2dQDpr7R5stzeCy1a5hrNOkZLg9zOTTPcth/6+FrOrRZP9SMdxtw3w==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/forms": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.0.5.tgz", + "integrity": "sha512-2zB1IuqYNJrjh7Og9J8f/AtjX3NHc3VVbt0rPw35ghqIU3aQLpOichdQ1y5QvMWic1UzZ7SjWXDU7RpKbm4iUA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/http": { + "version": "7.2.16", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.16.tgz", + "integrity": "sha512-yvjbNyzFSmmz4UTjCdy5M8mk0cZqf9TvSf8yN5UVIwtw4joyuUdlgJCuin0qSbQOKIf/JjHoofpO2JkPCGSNww==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-11.0.5.tgz", + "integrity": "sha512-EzGycD9ztTKAZB+kR+masNqCfGmU0vnKd/z33VLmeo9fo41t/YNCEQEEFz/pEl2dEwX/Wjou+3oyTYZIZz2uSA==", + "dev": true + }, + "@angular/platform-browser": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.0.5.tgz", + "integrity": "sha512-173JZHF3QS78hEscBxFZ/kX8KLjdaDhfAYi4Sh8daIKNUcDcyhqEy7wpAjWmCwdspL1QUtWKCrhZqrEVNGTpvA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/platform-browser-dynamic": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.0.5.tgz", + "integrity": "sha512-MFjpQcqkHOu8iTUMKVG6vfuOHwrRlgPBvkNucEbtXhTTYNlsw2mprxfUODYEu26EBUAh+FGttu8ZjclUGw4bVg==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/platform-server": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-11.0.5.tgz", + "integrity": "sha512-LBcFN68vMT20Kt5Q03+flefpGVU5WYrZMt3+k3CRyt+BjCTPrlgx3BgrwTde0AlJqs20rpESexIABQcnYgD5RQ==", + "requires": { + "domino": "^2.1.2", + "tslib": "^2.0.0", + "xhr2": "^0.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/router": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.0.5.tgz", + "integrity": "sha512-mSD4tbzuFH4uBb9vxPQHBUbkIMoWAfVUb7r9gtn3/deOxQbVh08f2gk2iWDN3OQLAa5mNHswuLByAYSw2rPbMA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@angular/service-worker": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-11.0.5.tgz", + "integrity": "sha512-5H3wJD9vQzhJqNes5N9fKmJp7ftv7UlqJZHewbwfZKd3xta7+mHXGFelMIdzsvb0WGkqPrW1mUgL0sIQ9DYKOg==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/compat-data": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", + "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", + "dev": true + }, + "@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/generator": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", + "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", + "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", + "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.12.5", + "@babel/helper-validator-option": "^7.12.1", + "browserslist": "^4.14.5", + "semver": "^5.5.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", + "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "regexpu-core": "^4.7.1" + } + }, + "@babel/helper-define-map": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "dev": true, + "requires": { + "@babel/types": "^7.12.7" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-module-imports": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-module-transforms": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", + "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-replace-supers": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", + "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/helpers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", + "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", + "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", + "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", + "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.11" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "requires": { + "@babel/types": "^7.12.10" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true + }, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/plugin-transform-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", + "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", + "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.12.1", + "@babel/helper-compilation-targets": "^7.12.1", + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.1", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.12.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.1", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.1", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.1", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.12.1", + "core-js-compat": "^3.6.2", + "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", + "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/traverse": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/generator": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@fortawesome/angular-fontawesome": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.4.0.tgz", + "integrity": "sha512-DYVXdCzwQo6d0CxVMRK+10LpBAvYN9xigWeQW4wKYq/Czd5es46nPMKixB5rHfNViECwwlM2gTM61K4DpxlJxg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz", + "integrity": "sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.32", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz", + "integrity": "sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.32" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.1.tgz", + "integrity": "sha512-pkTZIWn7iuliCCgV+huDfZmZb2UjslalXGDA2PcqOVUYJmYL11y6ooFiMJkJvUZu+xgAc1gZgQe+Px12mZF0CA==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.32" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz", + "integrity": "sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.32" + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, + "@jsdevtools/coverage-istanbul-loader": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", + "dev": true, + "requires": { + "convert-source-map": "^1.7.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", + "merge-source-map": "^1.1.0", + "schema-utils": "^2.7.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "@ngtools/webpack": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.0.5.tgz", + "integrity": "sha512-hM0LdOSlC6c7ij+BvIpAFbe7dpJhL+A51L5v6YbMA6aM0Sb/y+HpE2u34AHEQvute7cLe4EyOyvJ9jSinVAJhQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "11.0.5", + "enhanced-resolve": "5.3.1", + "webpack-sources": "2.0.1" + } + }, + "@nguniversal/module-map-ngfactory-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-8.1.1.tgz", + "integrity": "sha512-vXFydMTPFRfGjmtdwtbNhl4Pmfg580Yit0vzlTeb3ZC1v+TJKR2GzaWYUileWxS60FrgNF4/tkOEL5ouDDx6Bw==" + }, + "@ngx-translate/core": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-12.1.2.tgz", + "integrity": "sha512-ZudJsqIxTKlLmPoqK8gJY3UpMGujR0Xm7HfXL6AR79yGRS23QqpjAhMfx4v5qUCcHMmQ9/78bW8QJLfp31c7vQ==" + }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "@schematics/angular": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-11.0.5.tgz", + "integrity": "sha512-7p2wweoJYhim8YUy3ih1SrPGqRsa6+aEFbYgo9v4zt7b3tOva8SvkbC2alayK74fclzQ7umqa6xAwvWhy8ORvg==", + "dev": true, + "requires": { + "@angular-devkit/core": "11.0.5", + "@angular-devkit/schematics": "11.0.5", + "jsonc-parser": "2.3.1" + } + }, + "@schematics/update": { + "version": "0.1100.5", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.1100.5.tgz", + "integrity": "sha512-BYtKKuiWsrlc4FMW3bRyl4tm6lWNMTi8oql/mtkSgH7V5eMmaLDJtM+zDl+qyC/KHPxbHTfoHDapfv1tITSWjA==", + "dev": true, + "requires": { + "@angular-devkit/core": "11.0.5", + "@angular-devkit/schematics": "11.0.5", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.6", + "npm-package-arg": "^8.0.0", + "pacote": "9.5.12", + "semver": "7.3.2", + "semver-intersect": "1.4.0" + }, + "dependencies": { + "ini": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.6.tgz", + "integrity": "sha512-IZUoxEjNjubzrmvzZU4lKP7OnYmX72XRl3sqkfJhBKweKi5rnGi5+IUdlj/H1M+Ip5JQ1WzaDMOBRY90Ajc5jg==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "@swimlane/ngx-datatable": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@swimlane/ngx-datatable/-/ngx-datatable-19.0.0.tgz", + "integrity": "sha512-S8eknIQeZtr5aPZgBUItHmt+aQwrI32Hm3jvrxqJXK9J43oDzhIKT2qR1rre3s0XlBn6Zl8LAbmlKotQ5nnxnQ==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, + "@tweenjs/tween.js": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.4.0.tgz", + "integrity": "sha512-J3fzl1F6wvh8KXVVcIuHN12xi1ZDcPA/0Vix+ZcJYwZWVHUwfIqfvzYXXEw7ybeev6477KCTt9fKydU+ajUqcg==" + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/crypto-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.0.1.tgz", + "integrity": "sha512-6+OPzqhKX/cx5xh+yO8Cqg3u3alrkhoxhE5ZOdSEv0DOzJ13lwJ6laqGU0Kv6+XDMFmlnGId04LtY22PsFLQUw==", + "dev": true + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.6.tgz", + "integrity": "sha512-hpQHs+lmZ0uuCrGyqypdI1Ho7jRFolOBT6OkNdZPFziLSSEKvWu+VxWU6bGdNEA/hoV4jV8pdDeNx8EWlmfNAw==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.8.tgz", + "integrity": "sha512-d9p31r7Nxk0ZH0U39PTH0hiDlJ+qNVGjlt1ucOoTUptxb2v+Y5VMnsxfwN+i3hK4yQnqBi3FMmoMFcd1JHDxdg==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.7.tgz", + "integrity": "sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "optional": true + }, + "@types/selenium-webdriver": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", + "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", + "optional": true + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/tween.js": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/@types/tween.js/-/tween.js-17.2.0.tgz", + "integrity": "sha512-mOsqurEtFEzwgkVc/jDVE2XrjZBYTbrmDUyCr9GXmnfc6q5otokxFtKvSY/B21zgz9LVRIvRTawKczjKi57wrA==" + }, + "@types/webpack-sources": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.8.tgz", + "integrity": "sha512-JHB2/xZlXOjzjBB6fMOpH1eQAfsrpqVVIbneE0Rok16WXwFaznaI5vfg75U5WgGJm7V9W1c4xeRQDjX/zwvghA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "adjust-sourcemap-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", + "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + } + }, + "adm-zip": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", + "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==", + "optional": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } + } + }, + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "optional": true + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "app-root-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", + "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "optional": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "arity-n": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", + "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "optional": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "aspnet-prerendering": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aspnet-prerendering/-/aspnet-prerendering-3.0.1.tgz", + "integrity": "sha1-C252e0nkJeHT1ZYR+sgMnG9bAQA=", + "requires": { + "domain-task": "^3.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "dev": true, + "requires": { + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "backoff-rxjs": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/backoff-rxjs/-/backoff-rxjs-6.5.7.tgz", + "integrity": "sha512-j3pEcM5ivIxZhkZ+CcF8LVb5/jORNT4HCEnzavFfBkXSEJfx/WMtq79NKKp8Z0IDThgXxnlvzf41Gvajb4C2NQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "optional": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "bootstrap": { + "version": "5.0.0-beta2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0-beta2.tgz", + "integrity": "sha512-e+uPbPHqTQWKyCX435uVlOmgH9tUt0xtjvyOC7knhKgOS643BrQKuTo+KecGpPV7qlmOyZgCfaM4xxPWtDEN/g==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz", + "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001165", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.621", + "escalade": "^3.1.1", + "node-releases": "^1.1.67" + } + }, + "browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "optional": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "optional": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001173", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001173.tgz", + "integrity": "sha512-R3aqmjrICdGCTAnSXtNyvWYMK3YtV5jwudbq0T7nN9k4kmE4CBuwPqyJ+KBzepSTh0huivV2gLbSMEzTTmfeYw==", + "dev": true + }, + "canonical-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", + "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "optional": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz", + "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", + "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "dependencies": { + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "codelyzer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.1.tgz", + "integrity": "sha512-awBZXFcJUyC5HMYXiHzjr3D24tww2l1D1OqtfA9vUhEtYr32a65A+Gblm/OvsO+HuKLYzn8EDMw1inSM3VbxWA==", + "dev": true, + "requires": { + "app-root-path": "^2.2.1", + "aria-query": "^3.0.0", + "axobject-query": "2.0.2", + "css-selector-tokenizer": "^0.7.1", + "cssauron": "^1.4.0", + "damerau-levenshtein": "^1.0.4", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.7", + "sprintf-js": "^1.1.2" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.4" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compose-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", + "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=", + "dev": true, + "requires": { + "arity-n": "^1.0.4" + } + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz", + "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + }, + "core-js-compat": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", + "integrity": "sha512-LO8uL9lOIyRRrQmZxHZFl1RV+ZbcsAkFWTktn5SmH40WgLtSNYN4m4W2v9ONT147PxBY/XrRhrWq8TlvObyUjQ==", + "dev": true, + "requires": { + "browserslist": "^4.16.0", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", + "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" + }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", + "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^2.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.3", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.1", + "semver": "^7.3.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "css-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", + "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", + "dev": true, + "requires": { + "css": "^2.0.0" + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "dev": true + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "requires": { + "through": "X.X.X" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + }, + "dependencies": { + "css-tree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", + "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "optional": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "dependency-graph": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", + "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", + "dev": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "detect-passive-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/detect-passive-events/-/detect-passive-events-1.0.5.tgz", + "integrity": "sha512-foW7Q35wwOCxVzW0xLf5XeB5Fhe7oyRgvkBYdiP9IWgLMzjqUqTvsJv9ymuEWGjY6AoDXD3OC294+Z9iuOw0QA==" + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "optional": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domain-context": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/domain-context/-/domain-context-0.5.1.tgz", + "integrity": "sha1-MhxmpBBVmHUHsjlqzF8E5T+5l8Q=" + }, + "domain-task": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/domain-task/-/domain-task-3.0.3.tgz", + "integrity": "sha1-T+fXQ5rP55LWm/jmv6ax41aGLUU=", + "requires": { + "domain-context": "^0.5.1", + "is-absolute-url": "^2.1.0", + "isomorphic-fetch": "^2.2.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domino": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", + "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.634", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.634.tgz", + "integrity": "sha512-QPrWNYeE/A0xRvl/QP3E0nkaEvYUvH3gM04ZWYtIa6QlSpEetRlRI1xvQ7hiMIySHHEV+mwDSX8Kj4YZY6ZQAw==", + "dev": true + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "enhanced-resolve": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.1.tgz", + "integrity": "sha512-G1XD3MRGrGfNcf6Hg0LVZG7GIKcYkbfHa5QMxt1HDUTdYoXH0JR1xXyg+MaKLF73E9A27uWNVxvFivNRYeUB6w==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true + }, + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "optional": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "fastq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "fuse.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-5.2.3.tgz", + "integrity": "sha512-ld3AEgKtKnnXCtJavtygAb+aLlD5aVvLwTocXXBSStLA6JGFI6oMxTvumwh46N2/3gs3A7JNDu1px5F1/cq84g==" + }, + "genfun": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "optional": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", + "dev": true + }, + "html-escaper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", + "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "optional": true + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "optional": true + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "optional": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "optional": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "optional": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-api": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.6.tgz", + "integrity": "sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "compare-versions": "^3.4.0", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "optional": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "optional": true + } + } + }, + "jasmine-core": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", + "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "optional": true + }, + "jest-worker": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", + "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", + "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "optional": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "karma": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-5.0.2.tgz", + "integrity": "sha512-RpUuCuGJfN3WnjYPGIH+VBF8023Lfm3TQH6D1kcNL+FxtEPc2UUz/nVjbVAGXH4Pm+Q7FVOAQjdAeFUpXpQ3IA==", + "dev": true, + "requires": { + "body-parser": "^1.16.1", + "braces": "^3.0.2", + "chokidar": "^3.0.0", + "colors": "^1.1.0", + "connect": "^3.6.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "flatted": "^2.0.0", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^4.0.2", + "lodash": "^4.17.14", + "log4js": "^4.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "socket.io": "2.1.1", + "source-map": "^0.6.1", + "tmp": "0.0.33", + "ua-parser-js": "0.7.21", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", + "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", + "dev": true, + "requires": { + "istanbul-api": "^2.1.6", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", + "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "dev": true, + "requires": { + "jasmine-core": "^3.3" + } + }, + "karma-jasmine-html-reporter": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.2.tgz", + "integrity": "sha512-ILBPsXqQ3eomq+oaQsM311/jxsypw5/d0LnZXj26XkfThwq7jZ55A2CFSKJVA5VekbbOGvMyv7d3juZj0SeTxA==", + "dev": true + }, + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "dev": true + }, + "less": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/less/-/less-3.12.2.tgz", + "integrity": "sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q==", + "dev": true, + "requires": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-7.0.2.tgz", + "integrity": "sha512-7MKlgjnkCf63E3Lv6w2FvAEgLMx3d/tNBExITcanAq7ys5U8VPWT3F6xcRjYmdNfkoQ9udoVFb1r2azSiTnD6w==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "license-webpack-plugin": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.3.1.tgz", + "integrity": "sha512-yhqTmlYIEpZWA122lf6E0G8+rkn0AzoQ1OpzUKKs/lXUqG1plmGnwmkuuPlfggzJR5y6DLOdot/Tv00CC51CeQ==", + "dev": true, + "requires": { + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "optional": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log4js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", + "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "dev": true, + "requires": { + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.4", + "streamroller": "^1.0.6" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "loglevel": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "dev": true + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "optional": true + }, + "make-fetch-happen": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "requires": { + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" + } + }, + "messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==" + }, + "messageformat-parser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", + "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.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 + }, + "mini-css-extract-plugin": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.2.1.tgz", + "integrity": "sha512-G3yw7/TQaPfkuiR73MDcyiqhyP8SnbmLhUbpC76H+wtQxA6wfKhMCQOCb6wnPK0dQbjORAeOILQqEesg4/wF7A==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "native-request": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.8.tgz", + "integrity": "sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag==", + "dev": true, + "optional": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "ngx-autosize": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/ngx-autosize/-/ngx-autosize-1.8.4.tgz", + "integrity": "sha512-eswPninzx2H1AyZrSNS36wMd4eLg9Hce5Utc23J3dAGher/PNC+6X9MBTqsOnQGbQk7wV1dhlZZH3252VwB05A==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ngx-bootstrap": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-6.2.0.tgz", + "integrity": "sha512-5WKHo6/ltkenw4UyXZwED8rODCgp2RGbWurzYzZsF/gH1JO5SN7TJ+AL6kXYk6XM42sDA2WhN9Db+ZPNjiyHnA==" + }, + "ngx-clipboard": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-14.0.1.tgz", + "integrity": "sha512-y6fDrvAso1cbM+VvHgB2kJ3dcQ/EBPol33nLaqqKB1jNO/Kd3l17EHdXNW/oKY0wUKCHk7ZCuiinREgUHEYfXg==", + "requires": { + "ngx-window-token": ">=4.0.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, + "ngx-timeago": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ngx-timeago/-/ngx-timeago-2.0.0.tgz", + "integrity": "sha512-5kQZeTPCkegEcSooUu3W0BSOwvxEt1q6zjodwEOYoW/FxETC8Bk1zQF1QOPZLS+Nu3GLA+g1oPXMVBonM+CYQQ==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, + "ngx-toastr": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-11.3.3.tgz", + "integrity": "sha512-DbLFkSZHsVPuuIIrsY1ziEhdkFUQ0V1yG1N0+1nKXGI5QBVesEDxLUVtntjzxJcWw/uUV+bKApo//tGHHORabQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ngx-translate-messageformat-compiler": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.8.0.tgz", + "integrity": "sha512-A1Zg2sC0uCc1r8siT1M2DFcLhgjX6aEIu2g5NGnPh51KGtGqQqXHiXx2qCxz1U9sKMlYrvCZzfxzJ2kaCTtw+A==", + "requires": { + "tslib": "^1.10.0" + } + }, + "ngx-virtual-scroller": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ngx-virtual-scroller/-/ngx-virtual-scroller-4.0.3.tgz", + "integrity": "sha512-JBqUJ/f7GRCZDnI/JeiFoTmYR8rC/Hyv8L5I7ImePM6f/hwiFNRsrK8Abdd0E3TwklwgmZAK875te9XQJrgsyQ==", + "requires": { + "@tweenjs/tween.js": "17.4.0", + "@types/tween.js": "17.2.0" + } + }, + "ngx-window-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-4.0.0.tgz", + "integrity": "sha512-z6tS3UQoKULdABWHpE57l1xtoxFFzlwLe1n+nu9+xzCZUdSvkGqhb5dSje4NOVhA6mMOqzR4SctSBZARwqPPuQ==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node-fetch-npm": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", + "integrity": "sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.69", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", + "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", + "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", + "dev": true, + "requires": { + "semver": "^7.1.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.0.tgz", + "integrity": "sha512-/ep6QDxBkm9HvOhOg0heitSd7JHA1U7y1qhhlRlteYYAi9Pdb/ZV7FW5aHpkrpM8+P+4p/jjR8zCyKPBMBjSig==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.6", + "semver": "^7.0.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.0.tgz", + "integrity": "sha512-ygs4k6f54ZxJXrzT0x34NybRlLeZ4+6nECAIbr2i0foTnijtS1TJiyzpqtuUAJOps/hO0tNDr8fRV5g+BtRlTw==", + "dev": true, + "requires": { + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^8.0.0", + "semver": "^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "npm-registry-fetch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz", + "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-is": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", + "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "oidc-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.10.1.tgz", + "integrity": "sha512-/QB5Nl7c9GmT9ir1E+OVY3+yZZnuk7Qa9ZEAJqSvDq0bAyAU9KAgeKipTEfKjGdGLTeOLy9FRWuNpULMkfZydQ==", + "requires": { + "base64-js": "^1.3.0", + "core-js": "^2.6.4", + "crypto-js": "^3.1.9-1", + "uuid": "^3.3.2" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + } + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + } + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "optional": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "optional": true + } + } + }, + "ora": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz", + "integrity": "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.4.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "mute-stream": "0.0.8", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", + "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + } + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "requires": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } + } + }, + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", + "dev": true, + "requires": { + "ts-pnp": "^1.1.6" + } + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "dev": true, + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.0.4.tgz", + "integrity": "sha512-pntA9zIR14drQo84yGTjQJg1m7T0DkXR4vXYHBngiRZdJtEeCrojL6lOpqUanMzG375lIJbT4Yug85zC/AJWGw==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "dev": true, + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + } + } + }, + "protoduck": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "protractor": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.3.tgz", + "integrity": "sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA==", + "optional": true, + "requires": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "webdriver-manager": { + "version": "12.1.7", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.7.tgz", + "integrity": "sha512-XINj6b8CYuUYC93SG3xPkxlyUc3IJbD6Vvo75CVGuG9uzsefDzWQrhz0Lq8vbPxtb4d63CZdYophF8k8Or/YiA==", + "optional": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.7" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "resolve-url-loader": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", + "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", + "dev": true, + "requires": { + "adjust-sourcemap-loader": "3.0.0", + "camelcase": "5.3.1", + "compose-function": "3.0.3", + "convert-source-map": "1.7.0", + "es6-iterator": "2.0.3", + "loader-utils": "1.2.3", + "postcss": "7.0.21", + "rework": "1.0.1", + "rework-visit": "1.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "postcss": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", + "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rework": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", + "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", + "dev": true, + "requires": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + } + } + }, + "rework-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", + "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=", + "dev": true + }, + "rfdc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", + "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rollup": { + "version": "2.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz", + "integrity": "sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "requires": { + "tslib": "^1.9.0" + } + }, + "rxjs-compat": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.6.7.tgz", + "integrity": "sha512-szN4fK+TqBPOFBcBcsR0g2cmTTUF/vaFEOZNuSdfU8/pGFnNmmn2u8SystYXG1QMrjOPBc6XTKHMVfENDf6hHw==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sass": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.27.0.tgz", + "integrity": "sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==", + "dev": true, + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, + "sass-loader": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.5.tgz", + "integrity": "sha512-2LqoNPtKkZq/XbXNQ4C64GFEleSEHKv6NPSI+bMC/l+jpEXGJhiRYkAQToO24MR7NU4JRY2RpLpJ/gjo2Uf13w==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "optional": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "optional": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + } + }, + "selfsigned": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "dev": true, + "requires": { + "node-forge": "^0.10.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "semver-intersect": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", + "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", + "dev": true, + "requires": { + "semver": "^5.0.0" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "optional": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "smart-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "dev": true, + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "dev": true + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "sockjs-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "socks": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", + "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map-loader": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.2.tgz", + "integrity": "sha512-bjf6eSENOYBX4JZDfl9vVLNsGAQ6Uz90fLmOazcmMcyDYOBFsGxPNn83jXezWLY9bJsVAo1ObztxPcV8HAbjVA==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.2", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "source-map": "^0.6.1", + "whatwg-mimetype": "^2.3.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "optional": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "optional": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "speed-measure-webpack-plugin": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.3.tgz", + "integrity": "sha512-2ljD4Ch/rz2zG3HsLsnPfp23osuPBS0qPuz9sGpkNXTN1Ic4M+W9xB8l8rS8ob2cO4b1L+WTJw/0AJwWYVgcxQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "streamroller": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", + "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "dev": true, + "requires": { + "async": "^2.6.2", + "date-format": "^2.0.0", + "debug": "^3.2.6", + "fs-extra": "^7.0.1", + "lodash": "^4.17.14" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "stylus": { + "version": "0.54.8", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", + "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", + "dev": true, + "requires": { + "css-parse": "~2.0.0", + "debug": "~3.1.0", + "glob": "^7.1.6", + "mkdirp": "~1.0.4", + "safer-buffer": "^2.1.2", + "sax": "~1.2.4", + "semver": "^6.3.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "stylus-loader": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-4.3.1.tgz", + "integrity": "sha512-apDYJEM5ZpOAWbWInWcsbtI8gHNr/XYVcSY/tWqOUPt7M5tqhtwXVsAkgyiVjhuvw2Yrjq474a9H+g4d047Ebw==", + "dev": true, + "requires": { + "fast-glob": "^3.2.4", + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "optional": true + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "symbol-observable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", + "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", + "dev": true + }, + "tapable": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", + "dev": true + }, + "terser": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.7.tgz", + "integrity": "sha512-lJbKdfxWvjpV330U4PBZStCT9h3N9A4zZVA5Y4k9sCWXknrpdyxi1oMsRKLmQ/YDMDxSBKIh88v0SkdhdqX06w==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + } + } + }, + "terser-webpack-plugin": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.5.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.3.4", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "optional": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "ts-cacheable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ts-cacheable/-/ts-cacheable-1.0.4.tgz", + "integrity": "sha512-hiCFXXDDAaoh8ekZZkaAtzo+5j1V9BG9FIl38l9G3+lFo/LGF1u2GpnY6tjghBr7+Q7lh0J+wYjxaQYrluLewA==" + }, + "ts-node": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz", + "integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==", + "optional": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "optional": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "ts-pnp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", + "dev": true + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "optional": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "optional": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "optional": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "optional": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "optional": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", + "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "dev": true + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universal-analytics": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.23.tgz", + "integrity": "sha512-lgMIH7XBI6OgYn1woDEmxhGdj8yDefMKg7GkWdeATAlQZFrMrNyxSkpDzY57iY0/6fdlzTbBV03OawvvzG+q7A==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "request": "^2.88.2", + "uuid": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + }, + "dependencies": { + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + } + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "optional": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webpack": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.20", + "sockjs-client": "1.4.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "sockjs": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true, + "requires": { + "websocket-extensions": ">=0.1.1" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.2.0.tgz", + "integrity": "sha512-QBglJBg5+lItm3/Lopv8KDDK01+hjdg2azEwi/4vKJ8ZmGPdtJsTpjtNNOW3a4WiqzXdCATtTudOZJngE7RKkA==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.0.1.tgz", + "integrity": "sha512-A9oYz7ANQBK5EN19rUXbvNgfdfZf5U2gP0769OXsj9CvYkCR6OHOsd6OKyEy4H38GGxpsQPKIL83NC64QY6Xmw==", + "dev": true, + "requires": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-subresource-integrity": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.5.1.tgz", + "integrity": "sha512-uekbQ93PZ9e7BFB8Hl9cFIVYQyQqiXp2ExKk9Zv+qZfH/zHXHrCFAfw1VW0+NqWbTWrs/HnuDrto3+tiPXh//Q==", + "dev": true, + "requires": { + "webpack-sources": "^1.3.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "optional": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "worker-plugin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-5.0.0.tgz", + "integrity": "sha512-AXMUstURCxDD6yGam2r4E34aJg6kW85IiaeX72hi+I1cxyaMUtrvVY6sbfpGKAj5e7f68Acl62BjQF5aOOx2IQ==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xhr2": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.0.tgz", + "integrity": "sha512-BDtiD0i2iKPK/S8OAZfpk6tyzEDnKKSjxWHcMBVmh+LuqJ8A32qXTyOx+TVOg2dKvq6zGBq2sgKPkEeRs1qTRA==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "optional": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "optional": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "optional": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "zone.js": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz", + "integrity": "sha512-GkPiJL8jifSrKReKaTZ5jkhrMEgXbXYC+IPo1iquBjayRa0q86w3Dipjn8b415jpitMExe9lV8iTsv8tk3DGag==" + } + } +} diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..f89f463 --- /dev/null +++ b/app/package.json @@ -0,0 +1,79 @@ +{ + "name": "manta", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "build:ssr": "ng run manta:server:dev", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular-slider/ngx-slider": "^2.0.3", + "@angular/animations": "^11.0.5", + "@angular/cdk": "^11.0.3", + "@angular/common": "^11.0.5", + "@angular/compiler": "^11.0.5", + "@angular/core": "^11.0.5", + "@angular/forms": "^11.0.5", + "@angular/http": "^7.2.16", + "@angular/platform-browser": "^11.0.5", + "@angular/platform-browser-dynamic": "^11.0.5", + "@angular/platform-server": "^11.0.5", + "@angular/router": "^11.0.5", + "@angular/service-worker": "^11.0.5", + "@fortawesome/angular-fontawesome": "^0.4.0", + "@fortawesome/fontawesome-svg-core": "^1.2.27", + "@fortawesome/free-brands-svg-icons": "^5.12.1", + "@fortawesome/free-solid-svg-icons": "^5.12.1", + "@nguniversal/module-map-ngfactory-loader": "^8.1.1", + "@ngx-translate/core": "^12.1.2", + "@swimlane/ngx-datatable": "^19.0.0", + "aspnet-prerendering": "^3.0.1", + "backoff-rxjs": "^6.5.7", + "bootstrap": "5.0.0-beta2", + "core-js": "^3.3.3", + "crypto-js": "^4.0.0", + "fuse.js": "^5.1.0", + "messageformat": "^2.3.0", + "ngx-autosize": "^1.7.4", + "ngx-bootstrap": "^6.2.0", + "ngx-clipboard": "^14.0.1", + "ngx-timeago": "^2.0.0", + "ngx-toastr": "^11.3.3", + "ngx-translate-messageformat-compiler": "^4.5.0", + "ngx-virtual-scroller": "^4.0.3", + "oidc-client": "^1.9.1", + "rxjs": "^6.5.3", + "ts-cacheable": "^1.0.4", + "zone.js": "0.9.1" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^0.1100.5", + "@angular/cli": "^11.0.5", + "@angular/compiler-cli": "^11.0.5", + "@angular/language-service": "^11.0.5", + "@types/crypto-js": "^4.0.1", + "@types/jasmine": "~3.4.4", + "@types/jasminewd2": "~2.0.8", + "@types/node": "~12.11.6", + "codelyzer": "^5.2.0", + "jasmine-core": "~3.5.0", + "jasmine-spec-reporter": "~4.2.1", + "karma": "^5.0.2", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~2.1.0", + "karma-jasmine": "~2.0.1", + "karma-jasmine-html-reporter": "^1.4.2", + "typescript": "4.0.5", + "webpack-dev-server": "^3.10.3" + }, + "optionalDependencies": { + "protractor": "~5.4.2", + "ts-node": "~8.4.1", + "tslint": "~5.20.0" + } +} diff --git a/app/proxy.conf.js b/app/proxy.conf.js new file mode 100644 index 0000000..e67c588 --- /dev/null +++ b/app/proxy.conf.js @@ -0,0 +1,8 @@ +module.exports = { + '/api': { + target: 'https://localhost:8443', + secure: false, + pathRewrite: {'^/api': ''}, + logLevel: 'debug' + } +} \ No newline at end of file diff --git a/app/src/app/account/account-editor/account-editor.component.html b/app/src/app/account/account-editor/account-editor.component.html new file mode 100644 index 0000000..6eec434 --- /dev/null +++ b/app/src/app/account/account-editor/account-editor.component.html @@ -0,0 +1,91 @@ +
+
+ + +
+

Update your profile

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+ + + + +
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/account/account-editor/account-editor.component.scss b/app/src/app/account/account-editor/account-editor.component.scss new file mode 100644 index 0000000..cb5b88e --- /dev/null +++ b/app/src/app/account/account-editor/account-editor.component.scss @@ -0,0 +1,75 @@ +input +{ + height: auto; + padding: 0.5rem; +} + +.form-check +{ + display: flex; + align-items: center; + padding: 0; + margin: 0 0 0 1.5rem; + cursor: pointer; + flex-grow: 1; + + .form-check-input + { + margin-right: .5rem; + float: none; + width: 1.4em; + max-width: 1rem; + margin-bottom: .25rem; + cursor: inherit; + background-color: #0dc3e9; + border-color: #0dc3e9; + box-shadow: 0 0 0 1px rgb(12, 19, 33, .5) inset; + + &:checked + { + background-color: #ff9c07; + border-color: #ff9c07; + + &:not(:focus) + { + box-shadow: none; + } + } + + &:checked[type=radio] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%230c1321'/%3e%3c/svg%3e"); + } + + &:checked[type=checkbox] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230c1321' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + } + + &:focus + { + box-shadow: 0 0 0 0.25rem rgba(13, 195, 233, .5); + } + + &:checked:focus + { + box-shadow: 0 0 0 0.25rem rgba(255, 156, 7, .25); + } + } + + .form-check-label + { + cursor: inherit; + width: 100%; + padding: .75rem 0; + font-family: "Bebas Neue", sans-serif; + font-size: 1.2rem; + } +} + +.form-check-input:checked + .form-check-label, +.form-check-input:checked + .form-check-label .package-specs, +.form-check-input:checked + .form-check-label .h3 +{ + color: #ff9c07; +} diff --git a/app/src/app/account/account-editor/account-editor.component.spec.ts b/app/src/app/account/account-editor/account-editor.component.spec.ts new file mode 100644 index 0000000..4496470 --- /dev/null +++ b/app/src/app/account/account-editor/account-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccountEditorComponent } from './account-editor.component'; + +describe('AccountEditorComponent', () => { + let component: AccountEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AccountEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AccountEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/account/account-editor/account-editor.component.ts b/app/src/app/account/account-editor/account-editor.component.ts new file mode 100644 index 0000000..656e8ea --- /dev/null +++ b/app/src/app/account/account-editor/account-editor.component.ts @@ -0,0 +1,101 @@ +import { Component, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { AuthService } from '../../helpers/auth.service'; +import { UserInfo } from '../models/user-info'; +import { AccountService } from '../helpers/account.service'; +import { ToastrService } from 'ngx-toastr'; + +@Component({ + selector: 'app-account-editor', + templateUrl: './account-editor.component.html', + styleUrls: ['./account-editor.component.scss'] +}) +export class AccountEditorComponent implements OnInit +{ + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly authService: AuthService, + private readonly accountService: AccountService, + private readonly toastr: ToastrService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + + authService.userInfoUpdated$.subscribe(this.createForm.bind(this)); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm(userInfo: UserInfo) + { + this.editorForm = this.fb.group( + { + id: [userInfo.id], + firstName: [userInfo.firstName], + lastName: [userInfo.lastName], + companyName: [userInfo.companyName], + address: [userInfo.address], + postalCode: [userInfo.postalCode], + city: [userInfo.city], + state: [userInfo.state], + country: [userInfo.country], + email: [userInfo.email], + phone: [userInfo.phone], + cns: [userInfo.triton_cns_enabled] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close(response?: any) + { + this.save.next(response); + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + + const changes = this.editorForm.getRawValue(); + + const userInfo = new UserInfo(); + Object.assign(userInfo, changes); + userInfo.triton_cns_enabled = changes.cns; + + this.accountService.updateAccount(userInfo) + .subscribe(response => + { + this.authService.userInfo = response; + + this.close(response); + + this.working = false; + }, err => + { + this.toastr.error(err.error.message); + this.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } +} diff --git a/app/src/app/account/account.component.html b/app/src/app/account/account.component.html new file mode 100644 index 0000000..5419808 --- /dev/null +++ b/app/src/app/account/account.component.html @@ -0,0 +1,55 @@ +
+
+
+
+
+

My profile

+ + +
+ +
+
    +
  • + Name: {{ userInfo.firstName }} {{ userInfo.lastName }} +
  • +
  • + Username: {{ userInfo.login }} +
  • +
  • + Email: {{ userInfo.email }} +
  • +
  • + Phone: {{ userInfo.phone }} +
  • +
  • + Container Name Service: + + {{ userInfo.triton_cns_enabled ? 'enabled' : 'disabled' }} + +
  • +
+
+
+
+ +
+
+
+

My SSH keys

+ + +
+ +
+
    +
  1. + {{ userKey.name }}: {{ userKey.fingerprint }} +
  2. +
+
+
+
+
+
diff --git a/app/src/app/account/account.component.scss b/app/src/app/account/account.component.scss new file mode 100644 index 0000000..f18e2bf --- /dev/null +++ b/app/src/app/account/account.component.scss @@ -0,0 +1,42 @@ +ul, ol +{ + background-color: rgba(16, 21, 39, .75); + height: 100%; +} + +h4 +{ + margin-bottom: 0; +} + +.list-group-item +{ + background: none; + padding: 1rem; + border-color: rgb(61, 94, 142, .25); + color: #5a8cd8; + + b + { + color: #ff9c07; + } +} + +.card +{ + border: 1px solid rgba(0, 0, 0, 0.5); + background-color: rgba(16, 21, 39, 0.5); + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + transition: box-shadow 0.15s ease-out; + height: 100%; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgba(18, 203, 240, .4), 0 0 10px 3px #0e162a; + } + + .card-body + { + padding: 0; + } +} diff --git a/app/src/app/account/account.component.spec.ts b/app/src/app/account/account.component.spec.ts new file mode 100644 index 0000000..140216d --- /dev/null +++ b/app/src/app/account/account.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccountComponent } from './account.component'; + +describe('AccountComponent', () => { + let component: AccountComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AccountComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AccountComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/account/account.component.ts b/app/src/app/account/account.component.ts new file mode 100644 index 0000000..15d996b --- /dev/null +++ b/app/src/app/account/account.component.ts @@ -0,0 +1,90 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { AccountService } from './helpers/account.service'; +import { AuthService } from '../helpers/auth.service'; +import { first, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { UserInfo } from './models/user-info'; +import { UserKey } from './models/user-key'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { AccountEditorComponent } from './account-editor/account-editor.component'; +import { ToastrService } from 'ngx-toastr'; +import { SshKeyEditorComponent } from './ssh-key-editor/ssh-key-editor.component'; + +@Component({ + selector: 'app-account', + templateUrl: './account.component.html', + styleUrls: ['./account.component.scss'] +}) +export class AccountComponent implements OnInit, OnDestroy +{ + userInfo: UserInfo; + userKeys: UserKey[]; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly accountService: AccountService, + private readonly authService: AuthService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService) + { + //accountService.getUsers().subscribe(x => console.log(x)); + + accountService.getUserLimits().subscribe(x => console.log(x)); + + authService.userInfoUpdated$ + .pipe(takeUntil(this.destroy$)) + .subscribe(x => this.userInfo = x); + + accountService.getKeys().subscribe(x => this.userKeys = x); + } + + // ---------------------------------------------------------------------------------------------------------------- + showEditor() + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: {} + }; + + const modalRef = this.modalService.show(AccountEditorComponent, modalConfig); + modalRef.setClass('modal-lg'); + } + + // ---------------------------------------------------------------------------------------------------------------- + addSshKey() + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: {} + }; + + const modalRef = this.modalService.show(SshKeyEditorComponent, modalConfig); + + + modalRef.content.save.pipe(first()).subscribe(x => this.userKeys = [...this.userKeys, x]); + // this.accountService.addKey('test', + // 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAzf7Cbu8tPvxgwG3MhXK959F7TtsSCQQXb3jSPAJtQT+CltA+OYLod/ojclfQfnutIHUpqq6PsCD/nhxiF2JYkKWve7olJV6akvXQOGNLqRdXTcEouUhevLAQV3sB+YNvjr5FRpspNK8prAn7UU4vyZhCKBT8VAgwkio3u8eR/26XDNow1C9NXC6P+2BYWjjKbJCI41XpLFIzsmHBw+XZox+IbVg8mcVsWfdhEHRDyxM1HgvOKU9vkCwigmww9nsIatSQuM0jCtohQRkddc2DlfKieBmpeC/VqNoWE77iei/nVOcgIaLjwwevdCGHhwtSBmkE+W14JCwFbzl0yThL2w== rsa-key-20210314', + // 'ba:04:55:94:64:24:75:a4:b2:60:e5:bf:77:19:df:34') + // .subscribe(response => this.userKeys = [...this.userKeys, response], + // err => + // { + // this.toastr.error(err.error.message) + // }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit() + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/account/account.module.ts b/app/src/app/account/account.module.ts new file mode 100644 index 0000000..a072d55 --- /dev/null +++ b/app/src/app/account/account.module.ts @@ -0,0 +1,48 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { TranslateLoader } from '@ngx-translate/core'; +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { TranslateCompiler } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { AccountComponent } from './account.component'; +import { AccountEditorComponent } from './account-editor/account-editor.component'; +import { SshKeyEditorComponent } from './ssh-key-editor/ssh-key-editor.component'; + +@NgModule({ + declarations: [AccountComponent, AccountEditorComponent, SshKeyEditorComponent], + imports: [ + SharedModule, + RouterModule.forChild([ + { + path: '', + component: AccountComponent + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('account') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ] +}) +export class AccountModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/app/account/helpers/account.service.spec.ts b/app/src/app/account/helpers/account.service.spec.ts new file mode 100644 index 0000000..2ffad6f --- /dev/null +++ b/app/src/app/account/helpers/account.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AccountService } from './account.service'; + +describe('AccountService', () => { + let service: AccountService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AccountService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/account/helpers/account.service.ts b/app/src/app/account/helpers/account.service.ts new file mode 100644 index 0000000..4fc8df8 --- /dev/null +++ b/app/src/app/account/helpers/account.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; +import { UserInfo } from '../models/user-info'; +import { Observable } from 'rxjs'; +import { UserKey } from '../models/user-key'; + +@Injectable({ + providedIn: 'root' +}) +export class AccountService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + getUserInfo(): Observable + { + return this.httpClient.get(`/api/my`); + } + + // ---------------------------------------------------------------------------------------------------------------- + updateAccount(userInfo: UserInfo): Observable + { + return this.httpClient.post(`/api/my`, userInfo); + } + + // ---------------------------------------------------------------------------------------------------------------- + getUserLimits() + { + return this.httpClient.get(`/api/my/limits`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getKeys(): Observable + { + return this.httpClient.get(`/api/my/keys`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addKey(name: string, key: string, fingerprint: string): Observable + { + return this.httpClient.post(`/api/my/keys`, { name, key, fingerprint }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteKey(name: string) + { + return this.httpClient.delete(`/api/my/keys/${name}`); + } +} diff --git a/app/src/app/account/models/user-info.ts b/app/src/app/account/models/user-info.ts new file mode 100644 index 0000000..8704572 --- /dev/null +++ b/app/src/app/account/models/user-info.ts @@ -0,0 +1,6 @@ +import { User } from '../../security/models/user'; + +export class UserInfo extends User +{ + triton_cns_enabled: boolean; +} diff --git a/app/src/app/account/models/user-key.ts b/app/src/app/account/models/user-key.ts new file mode 100644 index 0000000..be5929a --- /dev/null +++ b/app/src/app/account/models/user-key.ts @@ -0,0 +1,6 @@ +export class UserKey +{ + fingerprint: string; + key: string; + name: string; +} diff --git a/app/src/app/account/ssh-key-editor/ssh-key-editor.component.html b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.html new file mode 100644 index 0000000..bac6aae --- /dev/null +++ b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.html @@ -0,0 +1,21 @@ +
+
+ + +
+

SSH key editor

+ +
+ + + +
+ +
+ +
+
+
+
diff --git a/app/src/app/account/ssh-key-editor/ssh-key-editor.component.scss b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.scss new file mode 100644 index 0000000..c8cbf86 --- /dev/null +++ b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.scss @@ -0,0 +1,18 @@ +p +{ + color: #ff9c07; + font-size: 1.15rem; +} + +.form-control +{ + + .form-control + { + margin-top: .5rem; + } +} + +textarea +{ + border-radius: 2rem; +} diff --git a/app/src/app/account/ssh-key-editor/ssh-key-editor.component.spec.ts b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.spec.ts new file mode 100644 index 0000000..9d2642b --- /dev/null +++ b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SshKeyEditorComponent } from './ssh-key-editor.component'; + +describe('SshKeyEditorComponent', () => { + let component: SshKeyEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SshKeyEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SshKeyEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/account/ssh-key-editor/ssh-key-editor.component.ts b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.ts new file mode 100644 index 0000000..7160fc7 --- /dev/null +++ b/app/src/app/account/ssh-key-editor/ssh-key-editor.component.ts @@ -0,0 +1,83 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { AccountService } from '../helpers/account.service'; +import { ToastrService } from 'ngx-toastr'; + +@Component({ + selector: 'app-ssh-key-editor', + templateUrl: './ssh-key-editor.component.html', + styleUrls: ['./ssh-key-editor.component.scss'] +}) +export class SshKeyEditorComponent implements OnInit, OnDestroy +{ + save = new Subject(); + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly accountService: AccountService, + private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly toastr: ToastrService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + name: [null, Validators.required], + key: [null, Validators.required], + fingerprint: [null, Validators.required] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + const sshKey = this.editorForm.getRawValue(); + + this.accountService.addKey(sshKey.name, sshKey.key, sshKey.fingerprint) + .subscribe(response => + { + this.save.next(response); + + this.modalRef.hide(); + }, + err => + { + this.toastr.error(err.error.message); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit() + { + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/app-routing.module.ts b/app/src/app/app-routing.module.ts new file mode 100644 index 0000000..b75f9e3 --- /dev/null +++ b/app/src/app/app-routing.module.ts @@ -0,0 +1,88 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { UnauthorizedComponent } from './pages/unauthorized/unauthorized.component'; +import { NotFoundComponent } from './pages/not-found/not-found.component'; +import { AuthGuardService } from './helpers/auth-guard.service'; + +const appRoutes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: 'dashboard', + }, + { + path: 'file-manager', + loadChildren: () => import('./file-manager/file-manager.module').then(x => x.FileManagerModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + data: + { + title: 'fileManager.title', + subTitle: 'fileManager.subTitle', + icon: 'folder' + } + }, + { + path: 'dashboard', + loadChildren: () => import('./instances/instances.module').then(x => x.InstancesModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + }, + { + path: 'catalog', + loadChildren: () => import('./catalog/catalog.module').then(x => x.CatalogModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + }, + { + path: 'volumes', + loadChildren: () => import('./volumes/volumes.module').then(x => x.VolumesModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + }, + { + path: 'networking', + loadChildren: () => import('./networking/networking.module').then(x => x.NetworkingModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + }, + { + path: 'security', + loadChildren: () => import('./security/security.module').then(x => x.SecurityModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + data: + { + title: 'security.title', + subTitle: 'security.subTitle', + icon: 'shield-alt' + } + }, + { + path: 'account', + loadChildren: () => import('./account/account.module').then(x => x.AccountModule), + canActivate: [AuthGuardService], + canLoad: [AuthGuardService], + data: + { + title: 'account.title', + subTitle: 'account.subTitle', + icon: 'user-cog' + } + }, + { + path: 'unauthorized', + component: UnauthorizedComponent + }, + { + path: '**', + component: NotFoundComponent + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/app/src/app/app.component.html b/app/src/app/app.component.html new file mode 100644 index 0000000..fd3199b --- /dev/null +++ b/app/src/app/app.component.html @@ -0,0 +1,57 @@ +
+
+
+
+ +
+ + + +
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+
diff --git a/app/src/app/app.component.scss b/app/src/app/app.component.scss new file mode 100644 index 0000000..dcd4de4 --- /dev/null +++ b/app/src/app/app.component.scss @@ -0,0 +1,200 @@ +:host +{ + height: 100%; + padding: 1rem; + display: block; +} + +.module-loading-spinner +{ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(9, 11, 23); + opacity: .5; + z-index: 1030; +} + +main, +.pusher, +.content +{ + height: 100%; +} + +main +{ + height: 100%; + border-radius: .5rem; + box-shadow: 0 0 0 6px rgba(38, 43, 80, .32), 0 0 0 11px rgba(26, 31, 60, .52), 0 0 0 15px rgba(17, 21, 48, .35); + perspective: 1500px; + position: relative; + position: relative; + overflow: hidden; + + &:after + { + content: ''; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: url('../assets/images/bg.jpg') #111530 no-repeat; + background-size: cover; + filter: blur(40px); + -webkit-filter: blur(40px); + z-index: -1; + opacity: .7; + } +} + +.content +{ + border-radius: .5rem; + background-color: rgba(17, 21, 48, .5); + border: 1px solid rgba(0, 0, 0, .3); +} + +.content, +.content-inner +{ + position: relative; +} + +.backdrop +{ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +.pusher +{ + position: relative; + left: 0; + height: 100%; + perspective: 1000px; + transition: transform .5s; + transform-style: preserve-3d; + box-shadow: 0 0 4px -2px #FFF, 3rem 1rem 3rem -1rem rgba(0, 0, 0, 0.3), 3px 2px 0 2px rgba(13, 16, 37, .4), 4px 3px 1px 2px rgba(0, 0, 0, 0.2); + border-radius: .5rem; + + &:before + { + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + background: linear-gradient(208deg, rgba(255, 255, 255, 0) 25%, rgba(255, 255, 255, 0.1) 50%, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.3) 100%); + border-radius: .5rem; + content: ''; + opacity: 0; + transition: opacity .5s; + pointer-events: none; + z-index: 1030; + } +} + +.menu +{ + position: absolute; + top: 0; + left: 0; + visibility: hidden; + min-width: 275px; + height: 100%; + background-color: rgba(16,21,39, .8); + transition: all .5s; + z-index: 2; + opacity: 1; + transform: translate3d(-100%, 0, 0); + + &:after + { + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,.2); + content: ''; + opacity: 1; + transition: all .5s; + display: none; + } +} + +.menu-open +{ + .menu + { + visibility: visible; + transform: translate3d(0, 0, 0); + box-shadow: 2rem 0 2rem -1rem rgba(0, 0, 0, .3); + + &:after + { + width: 0; + height: 0; + opacity: 0; + transition: opacity .5s, width .1s .5s, height .1s .5s; + } + } + + .pusher + { + transform: translate3d(30px, 0, -600px) rotateY(-20deg); + pointer-events: none; + + &:before + { + width: 100%; + height: 100%; + opacity: 1; + transform: rotate(0deg); + } + } +} + +header nav +{ + top: 0; +} + +.brand +{ + font-family: 'Bebas Neue', sans-serif; + font-size: 2rem; + line-height: 1; + color: #ff9c07; + + .icon + { + padding: .5rem .25rem; + margin: 0 .5rem .15rem 0; + line-height: 0; + color: #ff9c07; + text-decoration: none; + } + + p + { + margin-bottom: 0; + font-variant: normal; + font-size: 1rem; + opacity: .75; + } +} + +#accountMenuDropdown +{ + color: #FFF; + text-decoration: none; +} diff --git a/app/src/app/app.component.ts b/app/src/app/app.component.ts new file mode 100644 index 0000000..c9ae90b --- /dev/null +++ b/app/src/app/app.component.ts @@ -0,0 +1,82 @@ +import { Component } from '@angular/core'; +import { Router, RouteConfigLoadStart, RouteConfigLoadEnd, NavigationStart, NavigationEnd, ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { BsLocaleService } from 'ngx-bootstrap/datepicker'; +import { filter, map, mergeMap } from 'rxjs/operators'; +import { TokenService } from './helpers/token.service'; +import { AuthService } from './helpers/auth.service'; +import { UserInfo } from './account/models/user-info'; + +@Component({ + selector: 'app-root', + styleUrls: ['./app.component.scss'], + templateUrl: './app.component.html' +}) +export class AppComponent +{ + showProgress = false; + menuVisibility = false; + title: string; + subTitle: string; + icon: string | string[]; + userInfo: UserInfo; + routeChanged: boolean; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly router: Router, + private readonly activatedRoute: ActivatedRoute, + private readonly translate: TranslateService, + private readonly localeService: BsLocaleService, + private readonly authService: AuthService) + { + // This language will be used as a fallback when a translation isn't found in the current language + translate.setDefaultLang('en'); + + translate.use('en'); + + // NgxBootstrap locale + this.localeService.use('en'); + + router.events + .pipe(filter(x => x instanceof RouteConfigLoadStart || x instanceof RouteConfigLoadEnd)) + .subscribe(x => + { + this.showProgress = x instanceof RouteConfigLoadStart; + }); + + router.events + .pipe(filter(x => x instanceof NavigationStart)) + .subscribe(x => this.routeChanged = false); + + router.events + .pipe( + filter(x => x instanceof NavigationEnd), + map(() => activatedRoute), + map(route => + { + while (route.firstChild) + route = route.firstChild; + + return route; + }), + filter(route => route.outlet === 'primary'), + mergeMap(route => route.data) + ) + .subscribe(x => + { + this.title = x.title; + this.subTitle = x.subTitle; + this.icon = x.icon; + + this.routeChanged = true; + }); + + authService.userInfoUpdated$.subscribe(userInfo => this.userInfo = userInfo); + } + + // ---------------------------------------------------------------------------------------------------------------- + logOff() + { + this.authService.logout(); + } +} diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts new file mode 100644 index 0000000..9521257 --- /dev/null +++ b/app/src/app/app.module.ts @@ -0,0 +1,69 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { registerLocaleData } from '@angular/common'; +import localeEn from '@angular/common/locales/en'; + +import { TranslateModule, TranslateCompiler, TranslateLoader } from '@ngx-translate/core'; +import { MESSAGE_FORMAT_CONFIG, TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; +import { WebpackTranslateLoader } from './helpers/webpack-translate-loader.service'; + +import { SharedModule } from './shared.module'; +import { AppRoutingModule } from './app-routing.module'; + +import { AppComponent } from './app.component'; +import { DashboardComponent } from './pages/dashboard/dashboard.component'; +import { UnauthorizedComponent } from './pages/unauthorized/unauthorized.component'; +import { NotFoundComponent } from './pages/not-found/not-found.component'; +import { NavMenuComponent } from './components/nav-menu/nav-menu.component'; +import { AuthInterceptorService } from './helpers/auth-interceptor.service'; + +@NgModule({ + declarations: [ + AppComponent, + DashboardComponent, + UnauthorizedComponent, + NotFoundComponent, + NavMenuComponent + ], + imports: [ + BrowserAnimationsModule, + BrowserModule.withServerTransition({ appId: 'manta-portal' }), + HttpClientModule, + AppRoutingModule, + SharedModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: () => new WebpackTranslateLoader() + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + } + }) + ], + providers: [ + { + provide: MESSAGE_FORMAT_CONFIG, + useValue: { + locales: ['en', 'ro'] + } + }, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptorService, + multi: true + } + ], + bootstrap: [AppComponent] +}) +export class AppModule +{ + constructor() + { + registerLocaleData(localeEn, 'en'); + } +} diff --git a/app/src/app/app.server.module.ts b/app/src/app/app.server.module.ts new file mode 100644 index 0000000..cfb0e02 --- /dev/null +++ b/app/src/app/app.server.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { ServerModule } from '@angular/platform-server'; +import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader'; +import { AppComponent } from './app.component'; +import { AppModule } from './app.module'; + +@NgModule({ + imports: [AppModule, ServerModule, ModuleMapLoaderModule], + bootstrap: [AppComponent] +}) +export class AppServerModule { } diff --git a/app/src/app/catalog/catalog.component.html b/app/src/app/catalog/catalog.component.html new file mode 100644 index 0000000..0680b43 --- /dev/null +++ b/app/src/app/catalog/catalog.component.html @@ -0,0 +1 @@ + diff --git a/app/src/app/catalog/catalog.component.scss b/app/src/app/catalog/catalog.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/catalog/catalog.component.spec.ts b/app/src/app/catalog/catalog.component.spec.ts new file mode 100644 index 0000000..8e3dd80 --- /dev/null +++ b/app/src/app/catalog/catalog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CatalogComponent } from './catalog.component'; + +describe('CatalogComponent', () => { + let component: CatalogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CatalogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CatalogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/catalog.component.ts b/app/src/app/catalog/catalog.component.ts new file mode 100644 index 0000000..88de426 --- /dev/null +++ b/app/src/app/catalog/catalog.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-catalog', + templateUrl: './catalog.component.html', + styleUrls: ['./catalog.component.scss'] +}) +export class CatalogComponent implements OnInit +{ + images: any[]; + + // ---------------------------------------------------------------------------------------------------------------- + constructor() + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } +} diff --git a/app/src/app/catalog/catalog.module.ts b/app/src/app/catalog/catalog.module.ts new file mode 100644 index 0000000..e09f0b2 --- /dev/null +++ b/app/src/app/catalog/catalog.module.ts @@ -0,0 +1,99 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { TranslateLoader } from '@ngx-translate/core'; +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { TranslateCompiler } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { CatalogComponent } from './catalog.component'; +import { CustomImagesComponent } from './custom-images/custom-images.component'; +import { DockerImagesComponent } from './docker-images/docker-images.component'; +import { DockerRegistryComponent } from './docker-registry/docker-registry.component'; +import { DockerImageEditorComponent } from './docker-image-editor/docker-image-editor.component'; +import { DockerRegistryEditorComponent } from './docker-registry-editor/docker-registry-editor.component'; +import { CustomImageEditorComponent } from './custom-image-editor/custom-image-editor.component'; + +@NgModule({ + declarations: [ + CatalogComponent, + CustomImagesComponent, + DockerImagesComponent, + DockerRegistryComponent, + DockerImageEditorComponent, + DockerRegistryEditorComponent + ], + imports: [ + SharedModule, + RouterModule.forChild([ + { + path: '', + component: CatalogComponent, + children: [ + { + path: '', + redirectTo: 'custom-images' + }, + { + path: 'custom-images', + component: CustomImagesComponent, + data: + { + title: 'catalog.customImages.title', + subTitle: 'catalog.customImages.subTitle', + icon: 'layer-group' + } + }, + { + path: 'docker-images', + component: DockerImagesComponent, + data: + { + title: 'catalog.dockerImages.title', + subTitle: 'catalog.dockerImages.subTitle', + icon: ['fab', 'docker'] + } + }, + { + path: 'docker-registry', + component: DockerRegistryComponent, + data: + { + title: 'catalog.dockerRegistry.title', + subTitle: 'catalog.dockerRegistry.subTitle', + icon: ['fab', 'docker'] + } + }] + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('catalog') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ], + entryComponents: [ + DockerImageEditorComponent, + DockerRegistryEditorComponent, + CustomImageEditorComponent + ] +}) +export class CatalogModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html new file mode 100644 index 0000000..282bbf2 --- /dev/null +++ b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.html @@ -0,0 +1,24 @@ +
+
+ + +
+

Create image from machine

+ +

Fill in the name and version for a new image based on the "{{ instance.name }}" machine

+ + + + + + + +
+ +
+
+
+
diff --git a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.scss b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.scss new file mode 100644 index 0000000..441f4e6 --- /dev/null +++ b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.scss @@ -0,0 +1,30 @@ +h4 +{ + color: #00dcff; +} + +p +{ + color: #ff9c07; + font-size: 1.15rem; +} + +input, textarea +{ + height: auto; + padding: .5rem 1rem; + border-radius: 3rem; +} + +input, input:focus, input:active, +textarea, textarea:focus, textarea:active +{ + background: transparent; + border-color: #00e7ff; + color: #ff9c07; +} + +textarea +{ + resize: none; +} diff --git a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.spec.ts b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.spec.ts new file mode 100644 index 0000000..78d4b78 --- /dev/null +++ b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustomImageEditorComponent } from './custom-image-editor.component'; + +describe('CustomImageEditorComponent', () => { + let component: CustomImageEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CustomImageEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomImageEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts new file mode 100644 index 0000000..edf2a95 --- /dev/null +++ b/app/src/app/catalog/custom-image-editor/custom-image-editor.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-custom-image-editor', + templateUrl: './custom-image-editor.component.html', + styleUrls: ['./custom-image-editor.component.scss'] +}) +export class CustomImageEditorComponent implements OnInit +{ + @Input() + instance: any; + + save = new Subject(); + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + name: [null, Validators.required], + version: [null, Validators.required], + description: [null] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.save.next(this.editorForm.getRawValue()); + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } +} diff --git a/app/src/app/catalog/custom-images/custom-images.component.html b/app/src/app/catalog/custom-images/custom-images.component.html new file mode 100644 index 0000000..4bb2136 --- /dev/null +++ b/app/src/app/catalog/custom-images/custom-images.component.html @@ -0,0 +1,128 @@ +
+
+
+ + + +
+ + +
+ +
+ + +
+
+
+ +
+ Loading... +
+
+ +
+
+
+

+ You don't have any custom images yet +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionOSTypePublish dateStatus
+ {{ image.name }} + +
{{ image.description }}
+
+ {{ image.os }} + + {{ image.type }} + + {{ image.published_at ? (image.published_at | timeago) : '' }} + + {{ image.state }} + +
+ + +
+
+ No images match your search criteria +
+
+
+
+
diff --git a/app/src/app/catalog/custom-images/custom-images.component.scss b/app/src/app/catalog/custom-images/custom-images.component.scss new file mode 100644 index 0000000..6fb03b3 --- /dev/null +++ b/app/src/app/catalog/custom-images/custom-images.component.scss @@ -0,0 +1,45 @@ +.table-responsive +{ + background-color: rgba(16, 21, 39, 0.75); + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + transition: box-shadow 0.15s ease-out; + border-radius: .25rem; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgb(18 203 240 / 40%), 0 0 10px 3px #0e162a; + } + + .rule + { + text-transform: uppercase; + color: #3d5e8e; + } + + .highlight + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: normal; + } + + .text-truncate + { + max-width: 350px; + } + + .inline-list-item + .inline-list-item + { + padding-left: .25rem; + + &:before + { + content: attr(text); + color: #3d5e8e; + } + } +} diff --git a/app/src/app/catalog/custom-images/custom-images.component.spec.ts b/app/src/app/catalog/custom-images/custom-images.component.spec.ts new file mode 100644 index 0000000..a9879bb --- /dev/null +++ b/app/src/app/catalog/custom-images/custom-images.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustomImagesComponent } from './custom-images.component'; + +describe('CustomImagesComponent', () => { + let component: CustomImagesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CustomImagesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomImagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/custom-images/custom-images.component.ts b/app/src/app/catalog/custom-images/custom-images.component.ts new file mode 100644 index 0000000..f69b48d --- /dev/null +++ b/app/src/app/catalog/custom-images/custom-images.component.ts @@ -0,0 +1,191 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ColumnMode, SelectionType } from '@swimlane/ngx-datatable'; +import { CatalogService } from '../helpers/catalog.service'; +import { AuthService } from '../../helpers/auth.service'; +import { debounceTime, distinctUntilChanged, filter, first, map, switchMap, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { ToastrService } from 'ngx-toastr'; +import { CatalogImage } from '../models/image'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import Fuse from 'fuse.js'; +import { sortArray } from '../../helpers/utils.service'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component'; + +@Component({ + selector: 'app-custom-images', + templateUrl: './custom-images.component.html', + styleUrls: ['./custom-images.component.scss'] +}) +export class CustomImagesComponent implements OnInit, OnDestroy +{ + images: CatalogImage[] = []; + listItems: CatalogImage[] = []; + editorForm: FormGroup; + loadingIndicator = true; + + private destroy$ = new Subject(); + private readonly fuseJsOptions: {}; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly catalogService: CatalogService, + private readonly modalService: BsModalService, + private readonly authService: AuthService, + private readonly toastr: ToastrService, + private readonly fb: FormBuilder) + { + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: true, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'name', weight: .9 }, + { name: 'description', weight: .8 }, + { name: 'os', weight: .7 }, + { name: 'type', weight: .7 } + ] + }; + + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + searchTerm: [''], + sortProperty: ['name'] + }); + + this.editorForm.get('searchTerm').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('sortProperty').valueChanges + .pipe( + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + } + + // ---------------------------------------------------------------------------------------------------------------- + private applyFiltersAndSort() + { + let listItems: CatalogImage[] = null; + + const searchTerm = this.editorForm.get('searchTerm').value; + if (searchTerm.length >= 2) + { + const fuse = new Fuse(this.images, this.fuseJsOptions); + const fuseResults = fuse.search(searchTerm); + listItems = fuseResults.map(x => x.item as CatalogImage); + } + + if (!listItems) + listItems = [...this.images]; + + this.listItems = sortArray(listItems, this.editorForm.get('sortProperty').value); + } + + // ---------------------------------------------------------------------------------------------------------------- + setSortProperty(propertyName: string) + { + this.editorForm.get('sortProperty').setValue(propertyName); + } + + // ---------------------------------------------------------------------------------------------------------------- + clearSearch() + { + this.editorForm.get('searchTerm').setValue(''); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getCustomImages() + { + this.loadingIndicator = true; + + this.authService.userInfoUpdated$ + .pipe( + takeUntil(this.destroy$), + filter(userInfo => userInfo != null), + switchMap(userInfo => this.catalogService.getCustomImages(userInfo.id)) + ) + .subscribe(images => + { + this.images = images; + + this.applyFiltersAndSort(); + + this.loadingIndicator = false + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to retrieve the list of custom images ${errorDetails}`); + + this.loadingIndicator = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteCustomImage(image: CatalogImage) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${image.name}" image?`, + confirmButtonText: 'Yes, delete this image', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe(first()).subscribe(() => + { + this.toastr.info(`Removing machine "${image.name}"...`); + + this.catalogService.deleteImage(image.id) + .subscribe(() => + { + const index = this.images.findIndex(i => i.id === image.id); + if (index >= 0) + this.images.splice(index, 1); + + this.applyFiltersAndSort(); + + this.toastr.info(`The image "${image.name}" has been removed`); + }, + err => + { + this.toastr.error(`Failed to delete the "${image.name}" image ${err.error.message}`); + }); + }); + + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.getCustomImages(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } + +} diff --git a/app/src/app/catalog/docker-image-editor/docker-image-editor.component.html b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.html new file mode 100644 index 0000000..c50cce7 --- /dev/null +++ b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.html @@ -0,0 +1 @@ +

docker-image-editor works!

diff --git a/app/src/app/catalog/docker-image-editor/docker-image-editor.component.scss b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/catalog/docker-image-editor/docker-image-editor.component.spec.ts b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.spec.ts new file mode 100644 index 0000000..afc2788 --- /dev/null +++ b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DockerImageEditorComponent } from './docker-image-editor.component'; + +describe('DockerImageEditorComponent', () => { + let component: DockerImageEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DockerImageEditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DockerImageEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/docker-image-editor/docker-image-editor.component.ts b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.ts new file mode 100644 index 0000000..c069e18 --- /dev/null +++ b/app/src/app/catalog/docker-image-editor/docker-image-editor.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-docker-image-editor', + templateUrl: './docker-image-editor.component.html', + styleUrls: ['./docker-image-editor.component.scss'] +}) +export class DockerImageEditorComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/catalog/docker-images/docker-images.component.html b/app/src/app/catalog/docker-images/docker-images.component.html new file mode 100644 index 0000000..9ad2a6a --- /dev/null +++ b/app/src/app/catalog/docker-images/docker-images.component.html @@ -0,0 +1,57 @@ + + + + + + + + +
{{ value }}
+
+
+ + + + {{ value }} + + + + + +
+ {{ value }} +
+
+
+ + + + + + {{ value | date | timeago }} + + + + + + + + + + +
diff --git a/app/src/app/catalog/docker-images/docker-images.component.scss b/app/src/app/catalog/docker-images/docker-images.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/catalog/docker-images/docker-images.component.spec.ts b/app/src/app/catalog/docker-images/docker-images.component.spec.ts new file mode 100644 index 0000000..86010a4 --- /dev/null +++ b/app/src/app/catalog/docker-images/docker-images.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DockerImagesComponent } from './docker-images.component'; + +describe('DockerImagesComponent', () => { + let component: DockerImagesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DockerImagesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DockerImagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/docker-images/docker-images.component.ts b/app/src/app/catalog/docker-images/docker-images.component.ts new file mode 100644 index 0000000..9a2bf24 --- /dev/null +++ b/app/src/app/catalog/docker-images/docker-images.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { ColumnMode, SelectionType } from '@swimlane/ngx-datatable'; +import { CatalogService } from '../helpers/catalog.service'; + +@Component({ + selector: 'app-docker-images', + templateUrl: './docker-images.component.html', + styleUrls: ['./docker-images.component.scss'] +}) +export class DockerImagesComponent implements OnInit +{ + rows: any[] = []; + loadingIndicator = true; + selectionType = SelectionType; + columnMode = ColumnMode; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly catalogService: CatalogService) + { + + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + +} diff --git a/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.html b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.html new file mode 100644 index 0000000..9484d25 --- /dev/null +++ b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.html @@ -0,0 +1 @@ +

docker-registry-editor works!

diff --git a/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.scss b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.spec.ts b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.spec.ts new file mode 100644 index 0000000..b7ba06b --- /dev/null +++ b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DockerRegistryEditorComponent } from './docker-registry-editor.component'; + +describe('DockerRegistryEditorComponent', () => { + let component: DockerRegistryEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DockerRegistryEditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DockerRegistryEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.ts b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.ts new file mode 100644 index 0000000..344fa65 --- /dev/null +++ b/app/src/app/catalog/docker-registry-editor/docker-registry-editor.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-docker-registry-editor', + templateUrl: './docker-registry-editor.component.html', + styleUrls: ['./docker-registry-editor.component.scss'] +}) +export class DockerRegistryEditorComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/catalog/docker-registry/docker-registry.component.html b/app/src/app/catalog/docker-registry/docker-registry.component.html new file mode 100644 index 0000000..3f484d2 --- /dev/null +++ b/app/src/app/catalog/docker-registry/docker-registry.component.html @@ -0,0 +1 @@ +

docker-registry works!

diff --git a/app/src/app/catalog/docker-registry/docker-registry.component.scss b/app/src/app/catalog/docker-registry/docker-registry.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/catalog/docker-registry/docker-registry.component.spec.ts b/app/src/app/catalog/docker-registry/docker-registry.component.spec.ts new file mode 100644 index 0000000..9c56639 --- /dev/null +++ b/app/src/app/catalog/docker-registry/docker-registry.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DockerRegistryComponent } from './docker-registry.component'; + +describe('DockerRegistryComponent', () => { + let component: DockerRegistryComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DockerRegistryComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DockerRegistryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/docker-registry/docker-registry.component.ts b/app/src/app/catalog/docker-registry/docker-registry.component.ts new file mode 100644 index 0000000..381e874 --- /dev/null +++ b/app/src/app/catalog/docker-registry/docker-registry.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-docker-registry', + templateUrl: './docker-registry.component.html', + styleUrls: ['./docker-registry.component.scss'] +}) +export class DockerRegistryComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/catalog/helpers/catalog.service.spec.ts b/app/src/app/catalog/helpers/catalog.service.spec.ts new file mode 100644 index 0000000..21b1429 --- /dev/null +++ b/app/src/app/catalog/helpers/catalog.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CatalogService } from './catalog.service'; + +describe('CatalogService', () => { + let service: CatalogService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CatalogService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/helpers/catalog.service.ts b/app/src/app/catalog/helpers/catalog.service.ts new file mode 100644 index 0000000..69dddac --- /dev/null +++ b/app/src/app/catalog/helpers/catalog.service.ts @@ -0,0 +1,165 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Cacheable } from 'ts-cacheable'; +import { delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators'; +import { CatalogPackage } from '../models/package'; +import { CatalogImage } from '../models/image'; + +const cacheBuster$ = new Subject(); +const imagesCacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class CatalogService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getDataCenters(): Observable + { + return this.httpClient.get(`/api/my/datacenters`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getServices(): Observable + { + return this.httpClient.get(`/api/my/services`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getPackages(): Observable + { + return this.httpClient.get(`/api/my/packages`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getPackage(packageId: string): Observable + { + return this.httpClient.get(`/api/my/packages/${packageId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: imagesCacheBuster$ + }) + getImages(allStates = false): Observable + { + return this.httpClient.get(`/api/my/images?${allStates ? 'state=all' : ''}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: imagesCacheBuster$ + }) + getCustomImages(ownerId: string): Observable + { + return this.httpClient.get(`/api/my/images?$state=all&owner=${ownerId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: imagesCacheBuster$ + }) + getImage(id: string): Observable + { + return this.httpClient.get(`/api/my/images/${id}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getImageUntilExpectedState(image: CatalogImage, expectedStates: string[], maxRetries = 10): Observable + { + // Keep polling the image until it reaches the expected state + return this.httpClient.get(`/api/my/images/${image.id}`) + .pipe( + tap(x => image.state = x.state), + repeatWhen(x => + { + let retries = 0; + + return x.pipe(delay(3000), map(y => + { + if (retries++ === maxRetries) + throw { error: `Failed to retrieve the current status for image "${image.name}"` }; + + return y; + })); + }), + filter(x => expectedStates.includes(x.state)), + take(1) // needed to stop the repeatWhen loop + ); + } + + // ---------------------------------------------------------------------------------------------------------------- + createImage(instanceId: string, name: string, version: string, + description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable + { + return this.httpClient.post(`/api/my/images`, + { + machine: instanceId, + name, + version, + description, + homepage, + eula, + acl, + tags + }) + .pipe(tap(() => imagesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + importImage(sourceDataCenterId: string, imageId: string): Observable + { + // Copy the image with id from the source datacenter into this datacenter + return this.httpClient.post(`/api/my/images?action=import-from-datacenter`, + { + datacenter: sourceDataCenterId, + image: imageId + }) + .pipe(tap(() => imagesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + editImage(imageId: string, name: string, version: string, description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable + { + return this.httpClient.post(`/api/my/images/${imageId}?action=update`, + { + name, + version, + description, + homepage, + eula, + acl, + tags + }) + .pipe(tap(() => imagesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + cloneImage(imageId: string): Observable + { + // https://apidocs.joyent.com/cloudapi/#CloneImage + return this.httpClient.post(`/api/my/images/${imageId}?action=clone`, {}) + .pipe(tap(() => imagesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteImage(id: string): Observable + { + // Note: Caller must be the owner of the image + return this.httpClient.delete(`/api/my/images/${id}`) + .pipe(tap(() => imagesCacheBuster$.next())); + } +} diff --git a/app/src/app/catalog/images/images.component.html b/app/src/app/catalog/images/images.component.html new file mode 100644 index 0000000..68a8319 --- /dev/null +++ b/app/src/app/catalog/images/images.component.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + +
NameDescriptionOS
{{ image.name }}{{ image.description }}{{ image.os }} + +
diff --git a/app/src/app/catalog/images/images.component.scss b/app/src/app/catalog/images/images.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/catalog/images/images.component.spec.ts b/app/src/app/catalog/images/images.component.spec.ts new file mode 100644 index 0000000..d98752c --- /dev/null +++ b/app/src/app/catalog/images/images.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ImagesComponent } from './images.component'; + +describe('ImagesComponent', () => { + let component: ImagesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ImagesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ImagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/images/images.component.ts b/app/src/app/catalog/images/images.component.ts new file mode 100644 index 0000000..a1ab987 --- /dev/null +++ b/app/src/app/catalog/images/images.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { ColumnMode, SelectionType } from '@swimlane/ngx-datatable'; +import { CatalogService } from '../helpers/catalog.service'; + +@Component({ + selector: 'app-images', + templateUrl: './images.component.html', + styleUrls: ['./images.component.scss'] +}) +export class ImagesComponent implements OnInit +{ + @Output() + select = new EventEmitter(); + + images: any[]; + loadingIndicator = true; + selectionType = SelectionType; + columnMode = ColumnMode; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly catalogService: CatalogService) + { + catalogService.getImages().subscribe(x => + { + this.images = x; + + this.loadingIndicator = false; + }); + + catalogService.getDataCenters().subscribe(console.log); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } +} diff --git a/app/src/app/catalog/models/image.ts b/app/src/app/catalog/models/image.ts new file mode 100644 index 0000000..32cc46d --- /dev/null +++ b/app/src/app/catalog/models/image.ts @@ -0,0 +1,17 @@ +export class CatalogImage +{ + id: string; + name: string; + version: string; + description: string; + owner: string; + public: boolean; + published_at: Date; + state: string; + type: string; + os: string; + tags: { key: string; value: string }; + requirements: any; + homepage: string; + image_size: number; +} diff --git a/app/src/app/catalog/models/package.ts b/app/src/app/catalog/models/package.ts new file mode 100644 index 0000000..475c37c --- /dev/null +++ b/app/src/app/catalog/models/package.ts @@ -0,0 +1,20 @@ +export class CatalogPackage +{ + id: string; + name: string; + brand: string; + memory: number; + memorySize: string; + memorySizeLabel: string; + disk: number; + diskSize: string; + diskSizeLabel: string; + swap: number; + lwps: number; + vcpus: number; + version: string; + group: string; + description: string; + disks: any[]; + flexible_disk: boolean; +} diff --git a/app/src/app/catalog/packages/packages.component.html b/app/src/app/catalog/packages/packages.component.html new file mode 100644 index 0000000..f440ca4 --- /dev/null +++ b/app/src/app/catalog/packages/packages.component.html @@ -0,0 +1,56 @@ +
+
+ Loading... +
+
+ + +
+ +
+ + +
diff --git a/app/src/app/catalog/packages/packages.component.scss b/app/src/app/catalog/packages/packages.component.scss new file mode 100644 index 0000000..d2fb056 --- /dev/null +++ b/app/src/app/catalog/packages/packages.component.scss @@ -0,0 +1,151 @@ +:host +{ + flex-grow: 1; + display: flex; + flex-direction: column; + overflow: auto; +} + +.list-group:not(.select-list) +{ + overflow: auto; + border-radius: 0; + border-top: 1px solid rgba(13, 195, 233, .5); + + .list-group-item + { + border-radius: 0; + background: transparent; + color: #5a8cd8; + border-color: rgb(61, 94, 142, .25); + } +} + +.btn-group +{ + .btn + { + background: none; + border-radius: 0; + border-left: none; + border-right: none; + border-top: none; + border-bottom: 2px solid transparent; + transition: none; + color: #0dc3e9; + + &.active + { + border-bottom-color: #0dc3e9; + box-shadow: 0 -1rem 1.5rem -1.5rem inset; + text-shadow: 0 0 .5rem; + } + + &:focus:not(.active) + { + box-shadow: none; + } + } +} + +.form-check +{ + display: flex; + align-items: center; + padding: 0; + margin: 0 0 0 2.5rem; + cursor: pointer; + flex-grow: 1; + + .form-check-input + { + margin-right: .5rem; + float: none; + width: 1.4em; + max-width: 1rem; + margin-bottom: .25rem; + cursor: inherit; + background-color: #0dc3e9; + border-color: #0dc3e9; + box-shadow: 0 0 0 1px rgba(12, 19, 33, .5) inset; + + &:checked[type=radio] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%230c1321'/%3e%3c/svg%3e"); + background-color: #ff9c07; + border-color: #ff9c07; + + &:not(:focus) + { + box-shadow: none; + } + } + + &:focus + { + box-shadow: 0 0 0 0.25rem rgba(255, 156, 7, .25); + } + } + + .h3 + { + text-transform: uppercase; + display: block; + margin-bottom: 0; + line-height: 1; + font-size: 1.5rem; + color: #8881ff; + } + + .form-check-label + { + cursor: inherit; + width: 100%; + padding: .75rem .25rem; + color: #5a8cd8; + font-family: "Mukta", sans-serif; + text-transform: none; + } +} + +.form-check-input:checked + .form-check-label, +.form-check-input:checked + .form-check-label .package-specs, +.form-check-input:checked + .form-check-label .h3 +{ + color: #ff9c07; +} + +.list-group-item-action .h5 +{ + font-size: 2.5rem; + float: left; + margin: .5rem 0 -.25rem 0; + + small + { + font-size: .8rem; + margin-left: -.5rem; + } +} + +.package-specs +{ + display: inline-block; + padding: 2px 2px 2px 1rem; + font-size: .9rem; + width: 100px; + border-left: 1px solid rgba(61, 94, 142, 0.25); + position: relative; + margin: 0 .5rem; + flex-grow: 1; + flex-basis: 0; + white-space: nowrap; + + .title + { + position: absolute; + top: 0; + left: 1rem; + opacity: .75; + } +} diff --git a/app/src/app/catalog/packages/packages.component.spec.ts b/app/src/app/catalog/packages/packages.component.spec.ts new file mode 100644 index 0000000..0584cef --- /dev/null +++ b/app/src/app/catalog/packages/packages.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PackagesComponent } from './packages.component'; + +describe('PackagesComponent', () => { + let component: PackagesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PackagesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PackagesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/catalog/packages/packages.component.ts b/app/src/app/catalog/packages/packages.component.ts new file mode 100644 index 0000000..4078b93 --- /dev/null +++ b/app/src/app/catalog/packages/packages.component.ts @@ -0,0 +1,195 @@ +import { Component, OnInit, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import { OnDestroy } from '@angular/core/core'; +import { ReplaySubject, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { FileSizePipe } from '../../pipes/file-size.pipe'; +import { CatalogService } from '../helpers/catalog.service'; +import { CatalogImage } from '../../catalog/models/image'; + +@Component({ + selector: 'app-packages', + templateUrl: './packages.component.html', + styleUrls: ['./packages.component.scss'] +}) +export class PackagesComponent implements OnInit, OnDestroy, OnChanges +{ + @Input() + imageType: number; + + @Input() + image: CatalogImage; + + @Input() + package: string; + + @Output() + select = new EventEmitter(); + + packageGroups: any[]; + loadingIndicator: boolean; + selectedPackageGroup: string; + + private packages: {}; + private _selectedPackage: {}; + private destroy$ = new Subject(); + private onChanges$ = new ReplaySubject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly catalogService: CatalogService, + private readonly fileSizePipe: FileSizePipe) + { + this.getPackages(); + } + + // ---------------------------------------------------------------------------------------------------------------- + setPackageGroup(event, packageGroup: string) + { + this.selectedPackageGroup = packageGroup; + + if (!packageGroup) return; + + switch (packageGroup) + { + case 'cpu': + this.packages[packageGroup].sort((a, b) => (a.vcpus || 1) - (b.vcpus || 1)); + break; + + case 'disk': + this.packages[packageGroup].sort((a, b) => a.disk - b.disk); + break; + + case 'memory optimized': + this.packages[packageGroup].sort((a, b) => a.memory - b.memory); + break; + + default: + this.packages[packageGroup].sort((a, b) => ((a.vcpus || 1) - (b.vcpus || 1)) || (a.memory - b.memory) || (a.disk - b.disk)); + break; + } + } + + // ---------------------------------------------------------------------------------------------------------------- + set selectedPackage(value) + { + this._selectedPackage = value; + + this.select.next(value); + } + get selectedPackage() + { + return this._selectedPackage; + } + + // ---------------------------------------------------------------------------------------------------------------- + private getPackages() + { + this.loadingIndicator = true; + + this.catalogService.getPackages() + .subscribe(response => + { + if (this.packages) + return; + + this.packages = response.reduce((groups, pkg) => + { + let size = this.fileSizePipe.transform(pkg.memory * 1024 * 1024); + [pkg.memorySize, pkg.memorySizeLabel] = size.split(' '); + + size = this.fileSizePipe.transform(pkg.disk * 1024 * 1024); + [pkg.diskSize, pkg.diskSizeLabel] = size.split(' '); + + const groupName = pkg.group.toLowerCase() || 'standard'; + + const group = (groups[groupName] || []); + group.push(pkg); + groups[groupName] = group; + + return groups; + }, {}); + + this.setPackageGroups(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private setPackageGroups() + { + if (!this.packages || !this.image || !this.imageType) + return; + + // Setup the operating systems array-like object, sorted alphabetically + this.packageGroups = Object.keys(this.packages) + .filter(x => + { + this.packages[x].forEach(p => + { + if (p.name === this.package) + this._selectedPackage = p; + + if (!p.brand || !this.image) + { + p.visible = true; + return; + } + + if (this.image.requirements.brand) + p.visible = this.image.requirements.brand === p.brand; + + if (this.image.type === 'zone-dataset') + p.visible = ['joyent', 'joyent-minimal'].includes(p.brand); + + if (this.image.type === 'lx-dataset') + p.visible = p.brand === 'lx'; + + if (this.image.type === 'zvol') + p.visible = ['bhyve', 'kvm'].includes(p.brand); + }); + + switch (this.imageType | 0) + { + case 1: + return this.packages[x].length && (!x || ['cpu', 'disk', 'memory optimized', 'standard', 'triton'].includes(x)); + + case 2: + return this.packages[x].length && (!x || ['standard', 'triton'].includes(x)); + + default: + return false; + } + }) + .sort((a, b) => a.localeCompare(b)); + + // Set the pre-selected package group + this.selectedPackageGroup = this.packageGroups[0]; + + if (this.selectedPackage) + this.select.emit(this.selectedPackage); + + this.loadingIndicator = false; + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.onChanges$.pipe(takeUntil(this.destroy$)) + .subscribe((changes: SimpleChanges) => + { + if (changes.image?.currentValue && changes.imageType?.currentValue) + this.setPackageGroups(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnChanges(changes: SimpleChanges): void + { + // Since we can't control if ngOnChanges is executed before ngOnInit, we need this trick + this.onChanges$.next(changes); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy(): void + { + this.destroy$.next(); + } +} diff --git a/app/src/app/components/confirmation-dialog/confirmation-dialog.component.html b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.html new file mode 100644 index 0000000..a0d16ad --- /dev/null +++ b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.html @@ -0,0 +1,21 @@ +
+ + +
+

{{ title }}

+ +

{{ prompt }}

+ +
+ + + +
+
+
diff --git a/app/src/app/components/confirmation-dialog/confirmation-dialog.component.scss b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.scss new file mode 100644 index 0000000..87151a7 --- /dev/null +++ b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.scss @@ -0,0 +1,5 @@ +p +{ + color: #ff9c07; + font-size: 1.15rem; +} diff --git a/app/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts new file mode 100644 index 0000000..80bb525 --- /dev/null +++ b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmationDialogComponent } from './confirmation-dialog.component'; + +describe('ConfirmationDialogComponent', () => { + let component: ConfirmationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ConfirmationDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/components/confirmation-dialog/confirmation-dialog.component.ts b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.ts new file mode 100644 index 0000000..e6f1514 --- /dev/null +++ b/app/src/app/components/confirmation-dialog/confirmation-dialog.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-confirmation-dialog', + templateUrl: './confirmation-dialog.component.html', + styleUrls: ['./confirmation-dialog.component.scss'] +}) +export class ConfirmationDialogComponent implements OnInit, OnDestroy +{ + @Input() + title = 'Confirmation'; + + @Input() + prompt: string; + + @Input() + confirmButtonText = 'Yes'; + + @Input() + declineButtonText = 'No'; + + @Input() + confirmByDefault = true; + + confirm = new Subject(); + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + declineAction() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + confirmAction() + { + this.confirm.next(); + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit() + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/components/inline-editor/inline-editor.component.html b/app/src/app/components/inline-editor/inline-editor.component.html new file mode 100644 index 0000000..e30788f --- /dev/null +++ b/app/src/app/components/inline-editor/inline-editor.component.html @@ -0,0 +1,22 @@ + + +
+
+
+ +
+
+ +
+
+ + +
+
+
diff --git a/app/src/app/components/inline-editor/inline-editor.component.scss b/app/src/app/components/inline-editor/inline-editor.component.scss new file mode 100644 index 0000000..a4d50cb --- /dev/null +++ b/app/src/app/components/inline-editor/inline-editor.component.scss @@ -0,0 +1,20 @@ +.single-line +{ + resize: none; + white-space: nowrap; + overflow-x: auto; + overflow-y: hidden !important; +} + +.btn +{ + padding-top: .4em; +} + +.form-control +{ + background: #0c1321; + border-color: #00e7ff; + border-radius: 3rem; + color: #ff9c07; +} diff --git a/app/src/app/components/inline-editor/inline-editor.component.spec.ts b/app/src/app/components/inline-editor/inline-editor.component.spec.ts new file mode 100644 index 0000000..edff0e7 --- /dev/null +++ b/app/src/app/components/inline-editor/inline-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InlineEditorComponent } from './inline-editor.component'; + +describe('InlineEditorComponent', () => { + let component: InlineEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InlineEditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InlineEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/components/inline-editor/inline-editor.component.ts b/app/src/app/components/inline-editor/inline-editor.component.ts new file mode 100644 index 0000000..6ee228a --- /dev/null +++ b/app/src/app/components/inline-editor/inline-editor.component.ts @@ -0,0 +1,176 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; + +@Component({ + selector: 'app-inline-editor', + templateUrl: './inline-editor.component.html', + styleUrls: ['./inline-editor.component.scss'] +}) +export class InlineEditorComponent implements OnInit, OnDestroy +{ + @Input() + buttonTitle: string; + + @Input() + singleLine: boolean; + + @Input() + key: string; + + @Input() + keyLabel = 'Key'; + + @Input() + keyAllowedCharacters: string; + + @Input() + keyPattern: string; + + @Input() + value: string; + + @Input() + valueLabel = 'Value'; + + @Input() + valueAllowedCharacters: string; + + @Input() + valuePattern: string; + + @Input() + showValue = true; + + @Input() + disabled: boolean; + + @Output() + saved = new EventEmitter(); + + editorVisible: boolean; + editorForm: FormGroup; + + // -------------------------------------------------------------------------------------------------- + constructor(private readonly elementRef: ElementRef, + private readonly fb: FormBuilder) { } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + key: [this.key], + value: [this.value] + }); + + if (this.keyPattern) + this.editorForm.get('key').setValidators([Validators.required, Validators.pattern(this.keyPattern)]); + else + this.editorForm.get('key').setValidators([Validators.required]); + + if (this.valuePattern) + this.editorForm.get('value').setValidators([Validators.required, Validators.pattern(this.valuePattern)]); + else if (this.showValue) + this.editorForm.get('value').setValidators([Validators.required]); + } + + // -------------------------------------------------------------------------------------------------- + showEditor() + { + if (this.disabled) return; + + this.editorVisible = true; + + addEventListener('click', this.onDocumentClick.bind(this)); + } + + // -------------------------------------------------------------------------------------------------- + saveChanges() + { + event.preventDefault(); + event.stopPropagation(); + + this.editorVisible = false; + + this.removeEventListeners(); + + if (this.showValue) + this.saved.emit({ + key: this.editorForm.get('key').value, + value: this.editorForm.get('value').value + }); + else + this.saved.emit(this.editorForm.get('key').value); + + this.editorForm.get('key').setValue(null); + this.editorForm.get('value').setValue(null); + } + + // -------------------------------------------------------------------------------------------------- + cancelChanges() + { + this.editorVisible = false; + + this.removeEventListeners(); + + this.editorForm.get('key').setValue(null); + this.editorForm.get('value').setValue(null); + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('document:keydown.enter', ['$event']) + returnPressed(event) + { + if (event.currentTarget === this.elementRef.nativeElement && this.singleLine) + { + event.preventDefault(); + event.stopPropagation(); + } + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('document:keydown.escape', ['$event']) + escapePressed(event) + { + this.cancelChanges(); + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('input', ['$event']) + textEntered(event) + { + if (event.currentTarget === this.elementRef.nativeElement && this.singleLine && this.value) + this.editorForm.get('value').setValue(this.editorForm.get('value').value.replace(/\n/g, '')); + } + + // -------------------------------------------------------------------------------------------------- + protected onDocumentClick(event: MouseEvent) + { + if (!this.elementRef.nativeElement.contains(event.target)) + this.cancelChanges(); + } + + // -------------------------------------------------------------------------------------------------- + private removeEventListeners() + { + removeEventListener('click', this.onDocumentClick); + removeEventListener('input', this.textEntered); + removeEventListener('document:keydown.enter', this.returnPressed); + removeEventListener('document:keydown.escape', this.escapePressed); + } + + // -------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + if (!this.buttonTitle) + throw 'Specify a button title for the inline editor'; + + this.createForm(); + } + + // -------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.removeEventListeners(); + } +} diff --git a/app/src/app/components/nav-menu/nav-menu.component.html b/app/src/app/components/nav-menu/nav-menu.component.html new file mode 100644 index 0000000..869a13e --- /dev/null +++ b/app/src/app/components/nav-menu/nav-menu.component.html @@ -0,0 +1,68 @@ + diff --git a/app/src/app/components/nav-menu/nav-menu.component.scss b/app/src/app/components/nav-menu/nav-menu.component.scss new file mode 100644 index 0000000..1088307 --- /dev/null +++ b/app/src/app/components/nav-menu/nav-menu.component.scss @@ -0,0 +1,17 @@ +.dropdown-divider +{ + border-color: #222740; +} + +.navbar-dark .navbar-nav .nav-link +{ + color: #bfb7b1; +} + +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.active, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .show > .nav-link +{ + color: #f0ad4e; +} diff --git a/app/src/app/components/nav-menu/nav-menu.component.spec.ts b/app/src/app/components/nav-menu/nav-menu.component.spec.ts new file mode 100644 index 0000000..c6e15c5 --- /dev/null +++ b/app/src/app/components/nav-menu/nav-menu.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavMenuComponent } from './nav-menu.component'; + +describe('NavMenuComponent', () => { + let component: NavMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NavMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NavMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/components/nav-menu/nav-menu.component.ts b/app/src/app/components/nav-menu/nav-menu.component.ts new file mode 100644 index 0000000..65666b3 --- /dev/null +++ b/app/src/app/components/nav-menu/nav-menu.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { Router, NavigationStart } from '@angular/router'; +import { filter } from 'rxjs/operators'; +import { TokenService } from '../../helpers/token.service'; + +@Component({ + selector: 'app-nav-menu', + templateUrl: './nav-menu.component.html', + styleUrls: ['./nav-menu.component.scss'] +}) +export class NavMenuComponent implements OnInit +{ + @Output() + navigate = new EventEmitter(); + + isAuthenticated = false; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly router: Router, + private readonly tokenService: TokenService) + { + router.events + .pipe(filter(e => e instanceof NavigationStart)) + .subscribe(e => this.navigate.emit(e)); + + tokenService.accessTokenUpdated$.subscribe(x => this.isAuthenticated = !!x); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } +} diff --git a/app/src/app/components/prompt-dialog/prompt-dialog.component.html b/app/src/app/components/prompt-dialog/prompt-dialog.component.html new file mode 100644 index 0000000..762bcf3 --- /dev/null +++ b/app/src/app/components/prompt-dialog/prompt-dialog.component.html @@ -0,0 +1,19 @@ +
+
+ + +
+

{{ title }}

+ +

{{ prompt }}

+ + + +
+ +
+
+
+
diff --git a/app/src/app/components/prompt-dialog/prompt-dialog.component.scss b/app/src/app/components/prompt-dialog/prompt-dialog.component.scss new file mode 100644 index 0000000..87151a7 --- /dev/null +++ b/app/src/app/components/prompt-dialog/prompt-dialog.component.scss @@ -0,0 +1,5 @@ +p +{ + color: #ff9c07; + font-size: 1.15rem; +} diff --git a/app/src/app/components/prompt-dialog/prompt-dialog.component.spec.ts b/app/src/app/components/prompt-dialog/prompt-dialog.component.spec.ts new file mode 100644 index 0000000..abe3a6d --- /dev/null +++ b/app/src/app/components/prompt-dialog/prompt-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PromptDialogComponent } from './prompt-dialog.component'; + +describe('PromptDialogComponent', () => { + let component: PromptDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PromptDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PromptDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/components/prompt-dialog/prompt-dialog.component.ts b/app/src/app/components/prompt-dialog/prompt-dialog.component.ts new file mode 100644 index 0000000..0cd311d --- /dev/null +++ b/app/src/app/components/prompt-dialog/prompt-dialog.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-prompt-dialog', + templateUrl: './prompt-dialog.component.html', + styleUrls: ['./prompt-dialog.component.scss'] +}) +export class PromptDialogComponent implements OnInit, OnDestroy +{ + @Input() + title: string; + + @Input() + prompt: string; + + @Input() + saveButtonText = 'Save changes'; + + @Input() + value: string; + + @Input() + placeholder: string; + + @Input() + required: boolean; + + save = new Subject(); + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + value: [this.value] + }); + + if (this.required) + this.editorForm.get('value').setValidators(Validators.required); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.save.next(this.editorForm.get('value').value); + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit() + { + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/directives/alpha-only.directive.spec.ts b/app/src/app/directives/alpha-only.directive.spec.ts new file mode 100644 index 0000000..2c00a14 --- /dev/null +++ b/app/src/app/directives/alpha-only.directive.spec.ts @@ -0,0 +1,8 @@ +import { AlphaOnlyDirective } from './alpha-only.directive'; + +describe('AlphaOnlyDirective', () => { + it('should create an instance', () => { + const directive = new AlphaOnlyDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/app/src/app/directives/alpha-only.directive.ts b/app/src/app/directives/alpha-only.directive.ts new file mode 100644 index 0000000..6b5bac2 --- /dev/null +++ b/app/src/app/directives/alpha-only.directive.ts @@ -0,0 +1,53 @@ +import { Directive, ElementRef, HostListener, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core'; + +@Directive({ + selector: '[appAlphaOnly]' +}) +export class AlphaOnlyDirective implements OnInit, OnChanges +{ + @Input() + appAlphaOnly = '^[A-Za-z0-9]+$'; + + private regex; + //private negateRegex; + + // -------------------------------------------------------------------------------------------------- + constructor(private readonly el: ElementRef) { } + + // -------------------------------------------------------------------------------------------------- + @HostListener('keypress', ['$event']) + onKeyPress(event) + { + return this.regex ? this.regex.test(event.key) : true; + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('paste', ['$event']) + onPaste(event) + { + if (!this.regex) return; + + event.preventDefault(); + //const value = event.clipboardData.getData('text/plain').replace(this.negateRegex, ''); + const value = event.clipboardData.getData('text/plain').trim(); + if (value.match(this.regex)) + document.execCommand('insertHTML', false, value); + } + + // -------------------------------------------------------------------------------------------------- + ngOnInit() + { + if (!this.appAlphaOnly) return; + + this.regex = new RegExp(this.appAlphaOnly); + //this.regex = new RegExp(`^[${this.appAlphaOnly}]+$`); + //this.negateRegex = new RegExp(`[^${this.appAlphaOnly}]`, 'g'); + } + + // -------------------------------------------------------------------------------------------------- + ngOnChanges(changes: SimpleChanges): void + { + if (changes.appAlphaOnly.currentValue) + this.regex = new RegExp(this.appAlphaOnly); + } +} diff --git a/app/src/app/directives/autofocus.directive.spec.ts b/app/src/app/directives/autofocus.directive.spec.ts new file mode 100644 index 0000000..67b0120 --- /dev/null +++ b/app/src/app/directives/autofocus.directive.spec.ts @@ -0,0 +1,8 @@ +import { AutofocusDirective } from './autofocus.directive'; + +describe('AutofocusDirective', () => { + it('should create an instance', () => { + const directive = new AutofocusDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/app/src/app/directives/autofocus.directive.ts b/app/src/app/directives/autofocus.directive.ts new file mode 100644 index 0000000..ca33f6e --- /dev/null +++ b/app/src/app/directives/autofocus.directive.ts @@ -0,0 +1,64 @@ +import { Directive, Input, ElementRef, AfterViewInit, OnChanges, SimpleChanges } from '@angular/core'; + +@Directive({ + selector: '[appAutofocus]' +}) +export class AutofocusDirective implements AfterViewInit, OnChanges +{ + @Input() + appAutofocus = false; + + @Input() + appAutofocusDelay: number; + + @Input() + appFocusAnyElement: boolean; + + private focusElement: any; + + // -------------------------------------------------------------------------------------------------- + constructor(private readonly element: ElementRef) { } + + // -------------------------------------------------------------------------------------------------- + ngAfterViewInit(): void + { + if (this.element.nativeElement.nodeName === 'INPUT' || + this.element.nativeElement.nodeName === 'TEXTAREA' || + this.element.nativeElement.nodeName === 'SELECT') + this.focusElement = this.element.nativeElement; + else + this.focusElement = this.appFocusAnyElement + ? this.element.nativeElement + : this.element.nativeElement.querySelector('select, textarea, input'); + + this.setFocus(); + } + + // -------------------------------------------------------------------------------------------------- + ngOnChanges(changes: SimpleChanges): void + { + this.setFocus(); + } + + // -------------------------------------------------------------------------------------------------- + private setFocus() + { + if (!this.appAutofocus) return; + + setTimeout(() => + { + if (!this.focusElement) return; + + this.focusElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + + try + { + this.focusElement.select(); + } + catch (e) + { + this.focusElement.focus(); + } + }, (this.appAutofocusDelay | 0)); + } +} diff --git a/app/src/app/directives/lazy-load.directive.spec.ts b/app/src/app/directives/lazy-load.directive.spec.ts new file mode 100644 index 0000000..230c2a7 --- /dev/null +++ b/app/src/app/directives/lazy-load.directive.spec.ts @@ -0,0 +1,8 @@ +import { LazyLoadDirective } from './lazy-load.directive'; + +describe('LazyLoadDirective', () => { + it('should create an instance', () => { + const directive = new LazyLoadDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/app/src/app/directives/lazy-load.directive.ts b/app/src/app/directives/lazy-load.directive.ts new file mode 100644 index 0000000..ac07b0d --- /dev/null +++ b/app/src/app/directives/lazy-load.directive.ts @@ -0,0 +1,89 @@ +import { Directive, AfterViewInit, OnDestroy, ElementRef, Output, EventEmitter, Input } from '@angular/core'; + +@Directive({ + selector: '[lazyLoad]' +}) +export class LazyLoadDirective implements AfterViewInit, OnDestroy +{ + @Input() + container: any; + + @Input() + lazyLoadDelay: number; + + @Output() + canLoad = new EventEmitter(); + + @Output() + load = new EventEmitter(); + + @Output() + unload = new EventEmitter(); + + private observer: any; + private delay: any; + private options: {}; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly el: ElementRef) { } + + // ---------------------------------------------------------------------------------------------------------------- + private canLazyLoad = () => window && 'IntersectionObserver' in window; + + // ---------------------------------------------------------------------------------------------------------------- + private loadOnIntersection() + { + this.observer = new IntersectionObserver(entries => + { + entries.forEach(({ isIntersecting }) => + { + if (isIntersecting) + { + this.canLoad.emit(); + + this.delay = setTimeout(() => + { + this.load.emit(); + }, this.lazyLoadDelay); + } + else + { + clearTimeout(this.delay); + this.unload.emit(); + } + }); + }); + + this.observer.observe(this.el.nativeElement, this.options); + } + + // ---------------------------------------------------------------------------------------------------------------- + private loadWithDelay() + { + this.canLoad.emit(); + + this.delay = setTimeout(() => + { + this.load.emit(); + }, this.lazyLoadDelay); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngAfterViewInit() + { + this.options = { + threshold: 1.0, + rootMargin: '0px', + root: this.container + }; + + this.canLazyLoad() ? this.loadOnIntersection() : this.loadWithDelay(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + if (this.observer) + this.observer.unobserve(this.el.nativeElement); + } +} diff --git a/app/src/app/file-manager/file-manager.component.html b/app/src/app/file-manager/file-manager.component.html new file mode 100644 index 0000000..7731c9a --- /dev/null +++ b/app/src/app/file-manager/file-manager.component.html @@ -0,0 +1 @@ +

file-manager works!

diff --git a/app/src/app/file-manager/file-manager.component.scss b/app/src/app/file-manager/file-manager.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/file-manager/file-manager.component.spec.ts b/app/src/app/file-manager/file-manager.component.spec.ts new file mode 100644 index 0000000..9e94da7 --- /dev/null +++ b/app/src/app/file-manager/file-manager.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FileManagerComponent } from './file-manager.component'; + +describe('FileManagerComponent', () => { + let component: FileManagerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FileManagerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FileManagerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/file-manager/file-manager.component.ts b/app/src/app/file-manager/file-manager.component.ts new file mode 100644 index 0000000..a28fb79 --- /dev/null +++ b/app/src/app/file-manager/file-manager.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-file-manager', + templateUrl: './file-manager.component.html', + styleUrls: ['./file-manager.component.scss'] +}) +export class FileManagerComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/file-manager/file-manager.module.ts b/app/src/app/file-manager/file-manager.module.ts new file mode 100644 index 0000000..d6eb458 --- /dev/null +++ b/app/src/app/file-manager/file-manager.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { TranslateLoader } from '@ngx-translate/core'; +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { TranslateCompiler } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { FileManagerComponent } from './file-manager.component'; + +@NgModule({ + declarations: [FileManagerComponent], + imports: [ + SharedModule, + RouterModule.forChild([ + { + path: '', + component: FileManagerComponent + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('file-manager') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ] +}) +export class FileManagerModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/app/helpers/auth-guard.service.spec.ts b/app/src/app/helpers/auth-guard.service.spec.ts new file mode 100644 index 0000000..35afd37 --- /dev/null +++ b/app/src/app/helpers/auth-guard.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthGuardService } from './auth-guard.service'; + +describe('AuthGuardService', () => { + let service: AuthGuardService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthGuardService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/helpers/auth-guard.service.ts b/app/src/app/helpers/auth-guard.service.ts new file mode 100644 index 0000000..723bdb6 --- /dev/null +++ b/app/src/app/helpers/auth-guard.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { Router, CanActivate, CanLoad, ActivatedRouteSnapshot, RouterStateSnapshot, Route, UrlSegment } from '@angular/router'; +import { Observable } from 'rxjs'; +import { TokenService } from './token.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuardService implements CanActivate, CanLoad +{ + constructor(private readonly router: Router, + private readonly tokenService: TokenService) { } + + // ---------------------------------------------------------------------------------------------------------------- + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean + { + if (this.tokenService.accessTokenUpdated$.getValue()) + { + const redirectUrl = sessionStorage.getItem('redirectUrl'); + if (redirectUrl) + { + this.router.navigate([redirectUrl], { queryParamsHandling: '' }); + sessionStorage.removeItem('redirectUrl'); + } + + return true; + } + + sessionStorage.setItem('redirectUrl', state.url); + + return false; + } + + canLoad(route: Route, segments: UrlSegment[]): boolean | Observable | Promise + { + if (this.tokenService.accessTokenUpdated$.getValue()) + return true; + + this.router.navigate(['/unauthorized'], { state: { data: route.data } }); + return false; + } +} diff --git a/app/src/app/helpers/auth-interceptor.service.spec.ts b/app/src/app/helpers/auth-interceptor.service.spec.ts new file mode 100644 index 0000000..03e16c3 --- /dev/null +++ b/app/src/app/helpers/auth-interceptor.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthInterceptorService } from './auth-interceptor.service'; + +describe('AuthInterceptorService', () => { + let service: AuthInterceptorService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthInterceptorService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/helpers/auth-interceptor.service.ts b/app/src/app/helpers/auth-interceptor.service.ts new file mode 100644 index 0000000..ade2aa9 --- /dev/null +++ b/app/src/app/helpers/auth-interceptor.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse, HttpClient } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { TokenService } from './token.service'; +import { retryBackoff } from 'backoff-rxjs' + +@Injectable({ + providedIn: 'root' +}) +export class AuthInterceptorService implements HttpInterceptor +{ + constructor(private readonly httpClient: HttpClient, + private readonly router: Router, + private readonly tokenService: TokenService) { } + + intercept(req: HttpRequest, next: HttpHandler): Observable> + { + if (!req.url.includes('/api')) + return next.handle(req); + + if (sessionStorage.getItem('token')) + req = req.clone({ + setHeaders: { + 'X-Auth-Token': sessionStorage.getItem('token') + } + }); + + return next.handle(req) + .pipe( + catchError((err) => + { + if (err instanceof HttpErrorResponse && this.tokenService.token) + { + if (err.status === 401) + { + this.tokenService.token = null; + this.tokenService.prevAuthAttempt = null; + + this.httpClient.get(`/api/login`).subscribe((response: any) => window.location = response.url); + } + } + + return throwError(err); + }), + retryBackoff({ + initialInterval: 100, + maxRetries: 10, + resetOnSuccess: true, + shouldRetry: error => error.status === 408 + }) + ); + } +} diff --git a/app/src/app/helpers/auth.service.spec.ts b/app/src/app/helpers/auth.service.spec.ts new file mode 100644 index 0000000..f1251ca --- /dev/null +++ b/app/src/app/helpers/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/helpers/auth.service.ts b/app/src/app/helpers/auth.service.ts new file mode 100644 index 0000000..abaa9f9 --- /dev/null +++ b/app/src/app/helpers/auth.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TokenService } from './token.service'; +import { AccountService } from '../account/helpers/account.service'; +import { BehaviorSubject } from 'rxjs'; +import { UserInfo } from '../account/models/user-info'; +import { Router } from '@angular/router'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService +{ + userInfoUpdated$ = new BehaviorSubject(null); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly router: Router, + private readonly httpClient: HttpClient, + private readonly tokenService: TokenService, + private readonly accountService: AccountService) + { + const authToken = this.qs['token']; + + if (authToken) + tokenService.token = authToken; + else + this.login(); + + tokenService.accessTokenUpdated$.subscribe(token => + { + if (token) + accountService.getUserInfo().subscribe(userInfo => this.userInfoUpdated$.next(userInfo)); + else + this.userInfoUpdated$.next(null); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + login() + { + // Wait X seconds between login attempts + if (!this.tokenService.prevAuthAttempt || + !this.tokenService.token || + !this.tokenService.token && Math.abs(new Date().getTime() - this.tokenService.prevAuthAttempt) / 1000 > 60) + { + this.tokenService.prevAuthAttempt = new Date().getTime(); + + this.httpClient.get(`/api/login`).subscribe((response: any) => window.location = response.url); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + logout() + { + this.tokenService.token = null; + + this.login(); + } + + // ---------------------------------------------------------------------------------------------------------------- + set userInfo(value: UserInfo) + { + this.userInfoUpdated$.next(value); + } + + // ---------------------------------------------------------------------------------------------------------------- + private get qs() + { + const params = location.search.substr(1).split('&'); + + if (!params) + return {}; + + const result = {}; + for (let i = 0; i < params.length; ++i) + { + const p = params[i].split('=', 2); + if (p.length === 1) + result[p[0]] = ''; + else + result[p[0]] = decodeURIComponent(p[1].replace(/\+/g, ' ')); + } + + return result; + } +} diff --git a/app/src/app/helpers/token.service.spec.ts b/app/src/app/helpers/token.service.spec.ts new file mode 100644 index 0000000..7930902 --- /dev/null +++ b/app/src/app/helpers/token.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TokenService } from './token.service'; + +describe('TokenService', () => { + let service: TokenService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TokenService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/helpers/token.service.ts b/app/src/app/helpers/token.service.ts new file mode 100644 index 0000000..44780a6 --- /dev/null +++ b/app/src/app/helpers/token.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class TokenService +{ + accessTokenUpdated$ = new BehaviorSubject(this.token); + + // ---------------------------------------------------------------------------------------------------------------- + constructor() + { + } + + // ---------------------------------------------------------------------------------------------------------------- + get token(): string + { + return sessionStorage.getItem('token'); + } + + // ---------------------------------------------------------------------------------------------------------------- + set token(value: string) + { + if (value) + sessionStorage.setItem('token', value); + else + sessionStorage.removeItem('token'); + + this.accessTokenUpdated$.next(value); + } + + // ---------------------------------------------------------------------------------------------------------------- + get prevAuthAttempt(): number + { + const attempt = sessionStorage.getItem('prevAuthAttempt'); + if (attempt == null) + return new Date().getTime(); + + try + { + return JSON.parse(attempt) | 0; + } + catch(err) + { + return new Date().getTime(); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + set prevAuthAttempt(value: number) + { + sessionStorage.setItem('prevAuthAttempt', `${value}`); + } +} diff --git a/app/src/app/helpers/utils.service.spec.ts b/app/src/app/helpers/utils.service.spec.ts new file mode 100644 index 0000000..d70aee3 --- /dev/null +++ b/app/src/app/helpers/utils.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { UtilsService } from './utils.service'; + +describe('UtilsService', () => { + let service: UtilsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(UtilsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/helpers/utils.service.ts b/app/src/app/helpers/utils.service.ts new file mode 100644 index 0000000..bdeef37 --- /dev/null +++ b/app/src/app/helpers/utils.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class UtilsService +{ + constructor() { } +} + +export function sortArray(array: any[], sortProperty: string, sortAlphabetically = true): any[] +{ + return array.sort((a, b) => + { + if (typeof a[sortProperty] === 'string') + return a[sortProperty].localeCompare(b[sortProperty]); + + // equal items sort equally + if (a[sortProperty] === b[sortProperty]) + return 0; + + // nulls sort after anything else + if (a[sortProperty] == null) + return 1; + + if (b[sortProperty] == null) + return -1; + + // otherwise, if we're ascending, lowest sorts first + if (sortAlphabetically) + return a[sortProperty] < b[sortProperty] ? -1 : 1; + + // if descending, highest sorts first + return a[sortProperty] < b[sortProperty] ? 1 : -1; + }); +} diff --git a/app/src/app/helpers/webpack-translate-loader.service.ts b/app/src/app/helpers/webpack-translate-loader.service.ts new file mode 100644 index 0000000..45db1ec --- /dev/null +++ b/app/src/app/helpers/webpack-translate-loader.service.ts @@ -0,0 +1,20 @@ +import { TranslateLoader } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import { from } from 'rxjs'; + +export class WebpackTranslateLoader implements TranslateLoader +{ + private readonly moduleName: string; + + constructor(moduleName?: string) + { + this.moduleName = moduleName; + } + + getTranslation(lang: string): Observable + { + const moduleName = this.moduleName ? `/${this.moduleName}` : ''; + return from(import(`../../assets/i18n${moduleName}/${lang}.json`)); + } +} + diff --git a/app/src/app/instances/helpers/instances.service.spec.ts b/app/src/app/instances/helpers/instances.service.spec.ts new file mode 100644 index 0000000..2b7d2c2 --- /dev/null +++ b/app/src/app/instances/helpers/instances.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { InstancesService } from './instances.service'; + +describe('InstancesService', () => { + let service: InstancesService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(InstancesService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/helpers/instances.service.ts b/app/src/app/instances/helpers/instances.service.ts new file mode 100644 index 0000000..afa0fc1 --- /dev/null +++ b/app/src/app/instances/helpers/instances.service.ts @@ -0,0 +1,213 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Instance } from '../models/instance'; +import { delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators'; +import { InstanceRequest } from '../models/instance'; +import { Cacheable } from 'ts-cacheable'; +import { volumesCacheBuster$ } from '../../volumes/helpers/volumes.service'; + +const instancesCacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class InstancesService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: instancesCacheBuster$ + }) + get(): Observable + { + return this.httpClient.get(`/api/my/machines`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: instancesCacheBuster$ + }) + getById(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getInstanceUntilExpectedState(instance: Instance, expectedStates: string[], callbackFn?: InstanceCallbackFunction, maxRetries = 30): Observable + { + // Keep polling the instance until it reaches the expected state + return this.httpClient.get(`/api/my/machines/${instance.id}`) + .pipe( + tap(x => callbackFn && callbackFn(x)), + repeatWhen(x => + { + let retries = 0; + + return x.pipe( + delay(3000), + map(y => + { + if (retries++ === maxRetries) + throw { error: `Failed to retrieve the current status for machine "${instance.name}"` }; + + return y; + })); + }), + filter(x => expectedStates.includes(x.state)), + take(1) // needed to stop the repeatWhen loop + ); + } + + // ---------------------------------------------------------------------------------------------------------------- + add(instance: InstanceRequest): Observable + { + return this.httpClient.post(`/api/my/machines`, instance) + .pipe(tap(() => + { + instancesCacheBuster$.next(); + + if (instance.volumes?.length) + volumesCacheBuster$.next(); + })); + } + + // ---------------------------------------------------------------------------------------------------------------- + delete(instanceId: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}`) + .pipe(tap(() => instancesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + start(instanceId: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}?action=start`, {}) + .pipe(tap(() => instancesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + stop(instanceId: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}?action=stop`, {}) + .pipe(tap(() => instancesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + reboot(instanceId: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}?action=reboot`, {}) + .pipe(tap(() => instancesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + resize(instanceId: string, packageId: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}?action=resize&package=${packageId}`, {}) + .pipe(tap(() => instancesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + rename(instanceId: string, name: string): Observable + { + if (!name) + throw 'Name cannot be empty'; + + return this.httpClient.post(`/api/my/machines/${instanceId}?action=rename&name=${name}`, {}) + .pipe(tap(() => instancesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + toggleFirewall(instanceId: string, enable: boolean): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}?action=${enable ? 'enable' : 'disable'}_firewall`, {}); + } + + // ---------------------------------------------------------------------------------------------------------------- + toggleDeletionProtection(instanceId: string, enable: boolean): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}?action=${enable ? 'enable' : 'disable'}_deletion_protection`, {}); + } + + // ---------------------------------------------------------------------------------------------------------------- + getTags(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/tags`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getTag(instanceId: string, key: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/tags/${key}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addTags(instanceId: string, tags: any): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}/tags`, tags); + } + + // ---------------------------------------------------------------------------------------------------------------- + replaceTags(instanceId: string, tags: any): Observable + { + return this.httpClient.put(`/api/my/machines/${instanceId}/tags`, tags); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteAllTags(instanceId: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}/tags`); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteTag(instanceId: string, key: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}/tags/${key}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getMetadata(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/metadata`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getMetadataValue(instanceId: string, key: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/metadata/${key}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addMetadata(instanceId: string, metadata: any): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}/metadata`, metadata); + } + + // ---------------------------------------------------------------------------------------------------------------- + replaceMetadata(instanceId: string, metadata: any): Observable + { + return this.httpClient.put(`/api/my/machines/${instanceId}/metadata`, metadata); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteAllMetadata(instanceId: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}/metadata`); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteMetadata(instanceId: string, key: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}/metadata/${key}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getAudit(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/audit`); + } +} + +export type InstanceCallbackFunction = ((instance: Instance) => void); diff --git a/app/src/app/instances/helpers/migrations.service.spec.ts b/app/src/app/instances/helpers/migrations.service.spec.ts new file mode 100644 index 0000000..9ce89de --- /dev/null +++ b/app/src/app/instances/helpers/migrations.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MigrationsService } from './migrations.service'; + +describe('MigrationsService', () => { + let service: MigrationsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MigrationsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/helpers/migrations.service.ts b/app/src/app/instances/helpers/migrations.service.ts new file mode 100644 index 0000000..84fd315 --- /dev/null +++ b/app/src/app/instances/helpers/migrations.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Cacheable } from 'ts-cacheable'; +import { tap } from 'rxjs/operators'; + +const cacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class MigrationsService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getMigrations(): Observable + { + return this.httpClient.get(`/api/my/migrations`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getMigration(instanceId: string, migrationId: string): Observable + { + return this.httpClient.get(`/api/my/migrations/${migrationId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + migrate(instanceId: string): Observable + { + // https://apidocs.joyent.com/cloudapi/#Migrate + return this.httpClient.post(`/api/my/machines/${instanceId}/migrate`, { action: 'begin | sync | switch | automatic | pause | abort | watch', affinity: [] }) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + getMigrationProgress(instanceId: string): Observable + { + // https://apidocs.joyent.com/cloudapi/#Migrate + return this.httpClient.get(`/api/my/machines/${instanceId}/migrate?action=watch`); + } + + // ---------------------------------------------------------------------------------------------------------------- + finalizeMigration(instanceId: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}/migrate?action=finalize`, {}) + .pipe(tap(() => cacheBuster$.next())); + } +} diff --git a/app/src/app/instances/helpers/snapshots.service.spec.ts b/app/src/app/instances/helpers/snapshots.service.spec.ts new file mode 100644 index 0000000..55eded4 --- /dev/null +++ b/app/src/app/instances/helpers/snapshots.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { SnapshotsService } from './snapshots.service'; + +describe('SnapshotsService', () => { + let service: SnapshotsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(SnapshotsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/helpers/snapshots.service.ts b/app/src/app/instances/helpers/snapshots.service.ts new file mode 100644 index 0000000..c3a9d34 --- /dev/null +++ b/app/src/app/instances/helpers/snapshots.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Snapshot } from '../models/snapshot'; +import { Instance } from '../models/instance'; +import { delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators'; +import { Cacheable } from 'ts-cacheable'; + +const cacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class SnapshotsService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getSnapshots(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/snapshots`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getSnapshot(instanceId: string, snapshotName: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/snapshots/${encodeURIComponent(snapshotName)}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getSnapshotUntilExpectedState(instance: Instance, snapshot: Snapshot, expectedStates: string[], maxRetries = 10): Observable + { + // Keep polling the snapshot until it reaches the expected state + return this.httpClient.get(`/api/my/machines/${instance.id}/snapshots/${encodeURIComponent(snapshot.name)}`) + .pipe( + tap(x => snapshot.state = x.state), + repeatWhen(x => + { + let retries = 0; + + return x.pipe( + delay(3000), + map(() => + { + if (retries++ === maxRetries) + throw { error: { message: `Failed to retrieve the current status for snapshot "${snapshot.name}"` } }; + }) + ); + }), + filter(x => expectedStates.includes(x.state)), + take(1), // needed to stop the repeatWhen loop + tap(() => cacheBuster$.next()) + ); + } + + // ---------------------------------------------------------------------------------------------------------------- + createSnapshot(instanceId: string, snapshotName: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}/snapshots?name=${encodeURIComponent(snapshotName)}`, {}) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteSnapshot(instanceId: string, snapshotName: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}/snapshots/${encodeURIComponent(snapshotName)}`) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + startFromSnapshot(instanceId: string, snapshotName: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}/snapshots/${encodeURIComponent(snapshotName)}`, {}) + .pipe(tap(() => cacheBuster$.next())); + } +} diff --git a/app/src/app/instances/instance-history/instance-history.component.html b/app/src/app/instances/instance-history/instance-history.component.html new file mode 100644 index 0000000..faacd6a --- /dev/null +++ b/app/src/app/instances/instance-history/instance-history.component.html @@ -0,0 +1,48 @@ +
+
+ + +
+

Machine history

+ +
+
+ Loading... +
+
+ +
+ + + + + + + + + + + + + + + + + +
ActionTimeFinished
+ {{ info.action }} + {{ info.time | timeago }} + + {{ info.success }} + + + + +
+
+
+
+
diff --git a/app/src/app/instances/instance-history/instance-history.component.scss b/app/src/app/instances/instance-history/instance-history.component.scss new file mode 100644 index 0000000..c942fee --- /dev/null +++ b/app/src/app/instances/instance-history/instance-history.component.scss @@ -0,0 +1,20 @@ +fieldset +{ + height: 80vh; +} + +h4 +{ + color: #00dcff; +} + +b +{ + color: #ff9c07; + text-transform: uppercase; +} + +.table-responsive +{ + margin: 0 -1rem -1rem; +} diff --git a/app/src/app/instances/instance-history/instance-history.component.spec.ts b/app/src/app/instances/instance-history/instance-history.component.spec.ts new file mode 100644 index 0000000..be2a545 --- /dev/null +++ b/app/src/app/instances/instance-history/instance-history.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceHistoryComponent } from './instance-history.component'; + +describe('InstanceHistoryComponent', () => { + let component: InstanceHistoryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstanceHistoryComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceHistoryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-history/instance-history.component.ts b/app/src/app/instances/instance-history/instance-history.component.ts new file mode 100644 index 0000000..9b11085 --- /dev/null +++ b/app/src/app/instances/instance-history/instance-history.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { Instance } from '../models/instance'; +import { InstancesService } from '../helpers/instances.service'; +import { ToastrService } from 'ngx-toastr'; + +@Component({ + selector: 'app-instance-history', + templateUrl: './instance-history.component.html', + styleUrls: ['./instance-history.component.scss'] +}) +export class InstanceHistoryComponent implements OnInit, OnDestroy +{ + @Input() + instance: Instance; + + loading: boolean; + history: any[]; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly instancesService: InstancesService, + private readonly toastr: ToastrService) + { + // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getHistory() + { + this.loading = true; + + this.instancesService.getAudit(this.instance.id) + .subscribe(x => + { + this.history = x; + + this.loading = false; + }, err => + { + this.toastr.error(err.error.message); + this.loading = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.getHistory(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instance-info/instance-info.component.html b/app/src/app/instances/instance-info/instance-info.component.html new file mode 100644 index 0000000..52b6362 --- /dev/null +++ b/app/src/app/instances/instance-info/instance-info.component.html @@ -0,0 +1,37 @@ +
    +
  • + + {{ instance.id }} +
  • + +
  • +
    + + + {{ keyValue.value[0] }} + + + + {{ keyValue.value[1] }} + + + {{ keyValue.value[2] }} +
    + +
    + +
    +
  • + +
  • +
    + + +
    +
  • +
diff --git a/app/src/app/instances/instance-info/instance-info.component.scss b/app/src/app/instances/instance-info/instance-info.component.scss new file mode 100644 index 0000000..3325bfc --- /dev/null +++ b/app/src/app/instances/instance-info/instance-info.component.scss @@ -0,0 +1,27 @@ +.list-group-item +{ + background: none; + + span + { + display: inline-block; + position: relative; + + &:after + { + content: '.'; + color: #3d5e8e; + } + } + + b, .strong + { + color: #ff9c07; + font-weight: bold; + } + + .highlight + { + color: rgba(#ff9c07, .75); + } +} diff --git a/app/src/app/instances/instance-info/instance-info.component.spec.ts b/app/src/app/instances/instance-info/instance-info.component.spec.ts new file mode 100644 index 0000000..33c7d02 --- /dev/null +++ b/app/src/app/instances/instance-info/instance-info.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceInfoComponent } from './instance-info.component'; + +describe('InstanceInfoComponent', () => { + let component: InstanceInfoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstanceInfoComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-info/instance-info.component.ts b/app/src/app/instances/instance-info/instance-info.component.ts new file mode 100644 index 0000000..3d56262 --- /dev/null +++ b/app/src/app/instances/instance-info/instance-info.component.ts @@ -0,0 +1,119 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { CatalogService } from '../../catalog/helpers/catalog.service'; +import { empty, forkJoin, Observable, of, Subject } from 'rxjs'; +import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { InstancesService } from '../helpers/instances.service'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { Instance } from '../models/instance'; + +@Component({ + selector: 'app-instance-info', + templateUrl: './instance-info.component.html', + styleUrls: ['./instance-info.component.scss'] +}) +export class InstanceInfoComponent implements OnInit, OnDestroy +{ + @Input() + instance: Instance; + + @Input() + set loadInfo(value: boolean) + { + if (!this.finishedLoading && value && this.instance) + this.getInfo(); + } + + @Output() + beforeLoad = new EventEmitter(); + + @Output() + load = new EventEmitter(); + + loading: boolean; + + private finishedLoading: boolean; + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly instancesService: InstancesService, + private readonly catalogService: CatalogService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService) + { + } + + // ---------------------------------------------------------------------------------------------------------------- + toggleDeletionProtection(event, instance: Instance) + { + this.beforeLoad.emit(); + + this.instancesService.toggleDeletionProtection(instance.id, event.target.checked) + .subscribe(() => + { + this.toastr.info(`The deletion protection for machine "${instance.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`); + this.load.emit(); + }, + err => + { + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + this.load.emit(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + dnsCopied(event) + { + this.toastr.info('The DNS address has been copied to the clipboard'); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getInfo() + { + if (this.finishedLoading) return; + + this.loading = true; + + this.instancesService.getById(this.instance.id) + .subscribe(x => + { + const dnsList = {}; + for (const dns of x.dns_names.sort((a, b) => b.localeCompare(a))) + dnsList[dns] = this.getParsedDns(dns); + + this.instance.dnsList = dnsList; + + this.loading = false; + this.finishedLoading = true; + }, + err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Couldn't load details for machine "${this.instance.name}" ${errorDetails}`); + this.loading = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getParsedDns(dnsName: string): string[] + { + const dns = dnsName.toLowerCase(); + + const a = dns.split('.on.spearhead.cloud'); + const b = a[0].split('.inst.'); + const c = b[0].split('.'); + + return [c[0], c.length > 1 ? c[1] : '', `inst.${b[1]}.on.spearhead.cloud`]; + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instance-networks/instance-networks.component.html b/app/src/app/instances/instance-networks/instance-networks.component.html new file mode 100644 index 0000000..98027ec --- /dev/null +++ b/app/src/app/instances/instance-networks/instance-networks.component.html @@ -0,0 +1,86 @@ +
    +
  • +
    + + +
    + +
    + + +
    +
  • + +
  • +
    + Loading network details... +
    +
  • + + +
  • +
    + + + + + + + + + {{ nic.networkName }} + + + {{ nic.ip }} + / + + {{ nic.gateway }} + +
    + +
    + + +
    + + {{ nic.state }} +
  • +
    +
diff --git a/app/src/app/instances/instance-networks/instance-networks.component.scss b/app/src/app/instances/instance-networks/instance-networks.component.scss new file mode 100644 index 0000000..9050541 --- /dev/null +++ b/app/src/app/instances/instance-networks/instance-networks.component.scss @@ -0,0 +1,45 @@ +.list-group-item +{ + background: none; + color: #3d5e8e; + + .highlight + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: normal; + } +} + +.input-group-pill +{ + .form-select + { + background: transparent; + border-color: #0dcaf0; + border-radius: 1.5rem 0 0 1.5rem; + padding: .25rem .75rem; + color: #ff9c07; + + &:focus, &:active + { + box-shadow: 0 0 0 .25rem rgba(21, 204, 241,.25); + } + } + + .btn + { + border-radius: 0 1.5rem 1.5rem 0; + padding: .3rem .5rem; + line-height: 1; + } +} + +.btn.btn-outline-info, .form-check-label +{ + font-size: 1rem; +} diff --git a/app/src/app/instances/instance-networks/instance-networks.component.spec.ts b/app/src/app/instances/instance-networks/instance-networks.component.spec.ts new file mode 100644 index 0000000..10f2d68 --- /dev/null +++ b/app/src/app/instances/instance-networks/instance-networks.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceNetworksComponent } from './instance-networks.component'; + +describe('InstanceNetworksComponent', () => { + let component: InstanceNetworksComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstanceNetworksComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceNetworksComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-networks/instance-networks.component.ts b/app/src/app/instances/instance-networks/instance-networks.component.ts new file mode 100644 index 0000000..c0bb2dc --- /dev/null +++ b/app/src/app/instances/instance-networks/instance-networks.component.ts @@ -0,0 +1,299 @@ +import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs'; +import { delay, filter, first, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { NetworkingService } from '../../networking/helpers/networking.service'; +import { InstancesService } from '../helpers/instances.service'; +import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { Nic } from '../models/nic'; +import { Network } from '../../networking/models/network'; +import { Instance } from '../models/instance'; + +@Component({ + selector: 'app-instance-networks', + templateUrl: './instance-networks.component.html', + styleUrls: ['./instance-networks.component.scss'] +}) +export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges +{ + @Input() + instance: Instance; + + @Input() + loadNetworks: boolean; + + @Output() + beforeLoad = new EventEmitter(); + + @Output() + load = new EventEmitter(); + + @Output() + instanceReboot = new EventEmitter(); + + @Output() + instanceStateUpdate = new EventEmitter(); + + loading: boolean; + nics: Nic[]; + publicNetworks: Network[] = []; + fabricNetworks: Network[] = []; + finishedLoading: boolean; + + private destroy$ = new Subject(); + private onChanges$ = new ReplaySubject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly networkingService: NetworkingService, + private readonly instancesService: InstancesService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService) + { + this.networkingService.getNetworks() + .subscribe(networks => + { + for (const network of networks) + { + if (network.fabric) + this.fabricNetworks.push(network); + else + this.publicNetworks.push(network); + } + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to load the list of available networks for machine "${this.instance.name}" ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getNetworks() + { + this.loading = true; + + const observables = this.nics.map(x => this.networkingService.getNetwork(x.network)); + + forkJoin(observables) + .subscribe(networks => + { + for (const nic of this.nics) + { + nic.networkDetails = networks.find(x => x.id === nic.network); + nic.networkName = nic.networkDetails ? nic.networkDetails.name : ''; + } + + this.finishedLoading = true; + this.loading = false; + }, + err => + { + this.toastr.error(`Couldn't load network details: ${err.error.message}`); + this.loading = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + addNic(network: Network) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Connecting to a network will cause this machine to reboot. Do you wish to continue?`, + confirmButtonText: 'Yes, connect to this network', + declineButtonText: "No, don't connect" + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe( + first(), + tap(() => + { + this.beforeLoad.emit(); + + this.toastr.info(`Connecting machine "${this.instance.name}" to the "${network.name}" network...`); + }), + switchMap(() => + { + return this.networkingService.addNic(this.instance.id, network.id) + .pipe( + tap(x => + { + // Add the newly created NIC to the list, in its "provisioning" state + this.nics.unshift(x); + + if (this.instance.state === 'running') + this.instanceReboot.emit(); + }), + switchMap(x => + { + // Grab info about the newly created NIC's network + return this.networkingService.getNetwork(x.network) + .pipe( + map(y => ({ network: y, nic: x })) + ); + }), + switchMap(response => + { + // Keep polling the newly created NIC until it reaches its "running"/"stopped" state + return this.networkingService + .getNicUntilExpectedState(this.instance, response.nic, ['running', 'stopped'], n => this.nics[0].state = n.state) + .pipe( + takeUntil(this.destroy$), + map(y => ({ network: response.network, nic: y })) + ); + }) + ); + }) + ) + .subscribe(response => + { + if (this.nics.length && !this.nics[0].networkDetails) + { + const nic = this.nics[0]; + + nic.networkDetails = response.network; + nic.networkName = nic.networkDetails?.name || ''; + } + + this.toastr.info(`The machine "${this.instance.name}" has been connected to the "${network.name}" network`); + this.load.emit(); + }, + err => + { + // Remove the NIC that is hanging + if (this.nics.length && !this.nics[0].networkDetails) + this.nics.shift(); + + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to connect machine "${this.instance.name}" to the "${network.name}" network ${errorDetails}`); + this.load.emit(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteNic(nic: Nic) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Disconnecting from the "${nic.networkName}" network will reboot this machine. Do you wish to continue?`, + confirmButtonText: 'Yes, disconnect from this network', + declineButtonText: 'No, stay connected', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe( + first(), + tap(() => + { + this.beforeLoad.emit(); + + this.toastr.info(`Removing network interface "${nic.mac.toUpperCase()}" from machine "${this.instance.name}"...`); + }), + //filter(() => this.instance.state === 'running' || this.instance.state === 'stopped'), + switchMap(() => + { + return this.networkingService.deleteNic(this.instance.id, nic.mac) + .pipe( + takeUntil(this.destroy$), + tap(() => + { + if (this.instance.state === 'running') + this.instanceReboot.emit(); + }), + switchMap(() => + { + // If the machine is currently running, keep polling until it finishes restarting + return this.instance.state === 'running' + ? this.instancesService + .getInstanceUntilExpectedState(this.instance, ['running'], x => this.instanceStateUpdate.emit(x)) + .pipe(takeUntil(this.destroy$)) + : of(nic); + }) + ); + }), + switchMap(() => this.networkingService.getNics(this.instance.id)) + ).subscribe(nics => + { + const index = this.nics.findIndex(x => x.network === nic.network); + if (index >= 0) + this.nics.splice(index, 1); + + // Perhaps the list of NICs was updated in case the primary NIC was just removed, + // so another NIC was assigned as the primary one + for (const networkInterface of nics) + { + const found = this.nics.find(x => x.network === networkInterface.network); + if (found) + found.primary = networkInterface.primary; + } + + this.load.emit(); + + this.toastr.info(`The network interface has been removed from machine "${this.instance.name}"`); + }, err => + { + this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`); + this.load.emit(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + markNicAsReserved(nic: Nic) + { + + } + + // ---------------------------------------------------------------------------------------------------------------- + toggleCloudFirewall(event, instance: Instance) + { + instance.working = true; + + this.instancesService.toggleFirewall(instance.id, event.target.checked) + .subscribe(() => + { + this.toastr.info(`The cloud firewall for machine "${instance.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`); + instance.working = false; + }, + err => + { + instance.working = false; + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.nics = this.instance?.nics || []; + + this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() => + { + if (!this.finishedLoading && this.loadNetworks && this.instance) + this.getNetworks(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnChanges(changes: SimpleChanges): void + { + // Since we can't control if ngOnChanges is executed before ngOnInit, we need this trick + this.onChanges$.next(changes); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instance-security/instance-security.component.html b/app/src/app/instances/instance-security/instance-security.component.html new file mode 100644 index 0000000..e0fbf65 --- /dev/null +++ b/app/src/app/instances/instance-security/instance-security.component.html @@ -0,0 +1,18 @@ +
    +
  • +
    + Loading roles... +
    +
  • + +
  • +
    + + +
    +
  • +
diff --git a/app/src/app/instances/instance-security/instance-security.component.scss b/app/src/app/instances/instance-security/instance-security.component.scss new file mode 100644 index 0000000..dfd8ee7 --- /dev/null +++ b/app/src/app/instances/instance-security/instance-security.component.scss @@ -0,0 +1,21 @@ +.list-group-item +{ + background: none; + color: #3d5e8e; + + span + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: normal; + } +} + +.form-check-label +{ + font-size: 1rem; +} diff --git a/app/src/app/instances/instance-security/instance-security.component.spec.ts b/app/src/app/instances/instance-security/instance-security.component.spec.ts new file mode 100644 index 0000000..338d45d --- /dev/null +++ b/app/src/app/instances/instance-security/instance-security.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceSecurityComponent } from './instance-security.component'; + +describe('InstanceSecurityComponent', () => { + let component: InstanceSecurityComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstanceSecurityComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceSecurityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-security/instance-security.component.ts b/app/src/app/instances/instance-security/instance-security.component.ts new file mode 100644 index 0000000..85f16c1 --- /dev/null +++ b/app/src/app/instances/instance-security/instance-security.component.ts @@ -0,0 +1,125 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { CatalogService } from '../../catalog/helpers/catalog.service'; +import { InstancesService } from '../helpers/instances.service'; +import { Subject } from 'rxjs'; +import { delay, switchMap, takeUntil, tap } from 'rxjs/operators'; +import Fuse from 'fuse.js'; +import { SecurityService } from '../../security/helpers/security.service'; + +@Component({ + selector: 'app-instance-security', + templateUrl: './instance-security.component.html', + styleUrls: ['./instance-security.component.scss'] +}) +export class InstanceSecurityComponent implements OnInit, OnDestroy +{ + @Input() + instance: any; + + @Input() + set loadRoles(value: boolean) + { + if (value && this.instance && !this.roles) + this.getRoles(); + } + + loadingRoles: boolean; + filteredRoles: any[]; + roleName: string; + _searchTerm: string; + shouldSearch: boolean; + + private destroy$ = new Subject(); + private roles: any[]; + private readonly fuseJsOptions: {}; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly instancesService: InstancesService, + private readonly securityService: SecurityService, + private readonly toastr: ToastrService) + { + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: false, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'name', weight: .9 } + ] + }; + } + + // ---------------------------------------------------------------------------------------------------------------- + searchBoxFocused(isFocused = true) + { + if (isFocused) + this.shouldSearch = true; + else + this.shouldSearch = !!this.searchTerm; + } + + // ---------------------------------------------------------------------------------------------------------------- + get searchTerm() + { + return this._searchTerm; + } + set searchTerm(value: string) + { + this._searchTerm = value; + + if (!value) + { + this.filteredRoles = this.roles; + } + else + { + // Use fuzzy search for lookups + const fuse = new Fuse(this.roles, this.fuseJsOptions); + this.filteredRoles = fuse.search(value).map(x => x.item); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + setInstanceRole(event, role) + { + + } + + // ---------------------------------------------------------------------------------------------------------------- + private getRoles() + { + this.loadingRoles = true; + + this.securityService.getRoles() + .subscribe(x => + { + this.roles = x; + this.filteredRoles = x; + + this.loadingRoles = false; + }, + err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to retrieve the list of roles ${errorDetails}`); + this.loadingRoles = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + // TODO: Find a way to retrieve the list of RoleTags + //this.instancesService.getRoleTags(this.instance.id) + // .subscribe(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instance-snapshots/instance-snapshots.component.html b/app/src/app/instances/instance-snapshots/instance-snapshots.component.html new file mode 100644 index 0000000..617bcc4 --- /dev/null +++ b/app/src/app/instances/instance-snapshots/instance-snapshots.component.html @@ -0,0 +1,56 @@ +
    +
  • + + +
    + + +
    +
  • + +
  • +
    + Loading snapshots... +
    +
  • + +
  • +
    + {{ snapshot.name }} + {{ (snapshot.updated || snapshot.created) | timeago }} + {{ snapshot.size * 1024 * 1024 | fileSize }} +
    + +
    + + + + +
    + + {{ snapshot.state }} + restoring +
  • +
diff --git a/app/src/app/instances/instance-snapshots/instance-snapshots.component.scss b/app/src/app/instances/instance-snapshots/instance-snapshots.component.scss new file mode 100644 index 0000000..33c2eba --- /dev/null +++ b/app/src/app/instances/instance-snapshots/instance-snapshots.component.scss @@ -0,0 +1,82 @@ +.list-group-item +{ + background: none; + color: #3d5e8e; + + span + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: normal; + } +} + +.form-control +{ + background: transparent; + border-color: #0dcaf0; + border-radius: 1.5rem 0 0 1.5rem; + padding: .2rem .65rem; + color: #ff9c07; + + &:focus, &:active + { + box-shadow: 0 0 0 .25rem rgba(21, 204, 241,.25); + } +} + +.input-group-pill +{ + .btn + { + border-radius: 0 1.5rem 1.5rem 0; + padding: .3rem .5rem; + line-height: 1; + } +} + +.toolbar > .search-box +{ + transition: width .3s ease-in-out; + width: 2.1rem; + position: relative; + + .ng-fa-icon + { + position: absolute; + left: .5rem; + top: 50%; + transform: translateY(-50%); + z-index: 1; + pointer-events: none; + transition: opacity .15s ease-in-out; + } + + &.focus + { + outline: none; + width: 100%; + + .ng-fa-icon + { + opacity: 0; + } + + .form-control + { + padding-left: .65rem; + } + } + + .form-control + { + border-radius: 1.5rem; + padding-left: 1.3rem; + width: 100%; + } +} + diff --git a/app/src/app/instances/instance-snapshots/instance-snapshots.component.spec.ts b/app/src/app/instances/instance-snapshots/instance-snapshots.component.spec.ts new file mode 100644 index 0000000..30ba416 --- /dev/null +++ b/app/src/app/instances/instance-snapshots/instance-snapshots.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceSnapshotsComponent } from './instance-snapshots.component'; + +describe('InstanceSnapshotsComponent', () => { + let component: InstanceSnapshotsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstanceSnapshotsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceSnapshotsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-snapshots/instance-snapshots.component.ts b/app/src/app/instances/instance-snapshots/instance-snapshots.component.ts new file mode 100644 index 0000000..33d8e24 --- /dev/null +++ b/app/src/app/instances/instance-snapshots/instance-snapshots.component.ts @@ -0,0 +1,298 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { CatalogService } from '../../catalog/helpers/catalog.service'; +import { InstancesService } from '../helpers/instances.service'; +import { Subject } from 'rxjs'; +import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators'; +import Fuse from 'fuse.js'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component'; +import { Snapshot } from '../models/snapshot'; +import { SnapshotsService } from '../helpers/snapshots.service'; + +@Component({ + selector: 'app-instance-snapshots', + templateUrl: './instance-snapshots.component.html', + styleUrls: ['./instance-snapshots.component.scss'] +}) +export class InstanceSnapshotsComponent implements OnInit, OnDestroy +{ + @Input() + instance: any; + + @Input() + set loadSnapshots(value: boolean) + { + if (value && this.instance && !this.snapshots) + this.getSnapshots(); + } + + @Output() + beforeLoad = new EventEmitter(); + + @Output() + load = new EventEmitter(); + + @Output() + instanceStateUpdate = new EventEmitter(); + + loadingSnapshots: boolean; + filteredSnapshots: any[]; + snapshotName: string; + _searchTerm: string; + shouldSearch: boolean; + + private destroy$ = new Subject(); + private snapshots: any[]; + private readonly fuseJsOptions: {}; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly instancesService: InstancesService, + private readonly snapshotsService: SnapshotsService, + private readonly catalogService: CatalogService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService) + { + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: false, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'name', weight: .9 } + ] + }; + } + + // ---------------------------------------------------------------------------------------------------------------- + createSnapshot() + { + this.beforeLoad.emit(); + + this.snapshots = this.snapshots || []; + + // Spaces are not allowed in snapshot names (not documented)! + const snapshotName = this.snapshotName; + + // Clear this field + this.snapshotName = null; + + this.snapshotsService.createSnapshot(this.instance.id, snapshotName) + .pipe( + takeUntil(this.destroy$), + delay(1000), + tap(x => this.snapshots.unshift(x)), + switchMap((x: Snapshot) => this.snapshotsService.getSnapshotUntilExpectedState(this.instance, x, ['created']) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(x => + { + const index = this.snapshots.findIndex(s => s.name === snapshotName); + + if (index >= 0) + this.snapshots[index] = x; + + this.load.emit(); + this.toastr.info(`A new snapshot "${snapshotName}" has been created for machine "${this.instance.name}"`); + }, + err => + { + const index = this.snapshots.findIndex(s => s.name === snapshotName); + + // Remove this snapshot from the list since it couldn't be created + if (index >= 0) + this.snapshots.splice(index, 1); + + this.load.emit(); + this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + restoreSnapshot(snapshot: Snapshot) + { + this.confirmRestore(snapshot) + .subscribe(() => + { + this.beforeLoad.emit(); + + snapshot.working = true; + + // First we need to make sure the instance is stopped + if (this.instance.state !== 'stopped') + this.instancesService.stop(this.instance.id) + .pipe( + takeUntil(this.destroy$), + tap(() => this.toastr.info(`Restarting machine "${this.instance.name}"`)), + delay(1000), + switchMap(() => this.instancesService.getInstanceUntilExpectedState(this.instance, ['stopped'], x => this.instanceStateUpdate.emit(x)) + .pipe(takeUntil(this.destroy$)) + ) + ).subscribe(() => this.startMachineFromSnapshot(snapshot), + err => + { + snapshot.working = false; + this.load.emit(); + + this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`); + }); + else + this.startMachineFromSnapshot(snapshot); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private confirmRestore(snapshot: Snapshot) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Restoring the "${snapshot.name}" snapshot will reboot your machine. Do you wish to continue?`, + confirmButtonText: 'Yes, reboot and restore', + declineButtonText: "No, don't restore" + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + return modalRef.content.confirm.pipe(first()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private startMachineFromSnapshot(snapshot: Snapshot) + { + this.beforeLoad.emit(); + + this.toastr.info(`Restoring machine "${this.instance.name}" from "${snapshot.name}" snapshot`); + + this.snapshotsService.startFromSnapshot(this.instance.id, snapshot.name) + .pipe( + takeUntil(this.destroy$), + delay(1000), + switchMap(() => this.instancesService.getInstanceUntilExpectedState(this.instance, ['running'], x => this.instanceStateUpdate.emit(x), 20) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(() => + { + snapshot.working = false; + + this.load.emit(); + + this.toastr.info(`The machine "${this.instance.name}" has been started from the "${snapshot.name}" snapshot`); + }, err => + { + snapshot.working = false; + + this.load.emit(); + + this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteSnapshot(snapshot: Snapshot) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${snapshot.name}" snapshot?`, + confirmButtonText: 'Yes, delete this snapshot', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe(first()).subscribe(() => + { + this.beforeLoad.emit(); + + this.snapshotsService.deleteSnapshot(this.instance.id, snapshot.name) + .subscribe(() => + { + const index = this.snapshots.findIndex(s => s.name === snapshot.name); + if (index >= 0) + this.snapshots.splice(index, 1); + + this.load.emit(); + + this.toastr.info(`The "${snapshot.name}" snapshot has been deleted`); + }, err => + { + this.load.emit(); + + this.toastr.error(`The "${snapshot.name}" snapshot couldn't be deleted: ${err.error.message}`); + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getSnapshots() + { + this.loadingSnapshots = true; + + // Get the list of snapshots + this.snapshotsService.getSnapshots(this.instance.id) + .subscribe(x => + { + this.snapshots = x; + this.filteredSnapshots = x; + this.loadingSnapshots = false; + }, + err => + { + this.toastr.error(err.error.message); + this.loadingSnapshots = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + searchBoxFocused(isFocused = true) + { + if (isFocused) + this.shouldSearch = true; + else + this.shouldSearch = !!this.searchTerm; + } + + // ---------------------------------------------------------------------------------------------------------------- + get searchTerm() + { + return this._searchTerm; + } + set searchTerm(value: string) + { + this._searchTerm = value; + + if (!value) + { + this.filteredSnapshots = this.snapshots; + } + else + { + // Use fuzzy search for lookups + const fuse = new Fuse(this.snapshots, this.fuseJsOptions); + this.filteredSnapshots = fuse.search(value).map(x => x.item); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.html b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.html new file mode 100644 index 0000000..5b9e703 --- /dev/null +++ b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.html @@ -0,0 +1,54 @@ +
+
+ + +
+

+ Machine metadata + Machine tags +

+ +
+

Add a new metadata by specifying its key and value

+

Add a new tag by specifying its key and value

+ +
+ + + + +
+ +
    +
  • +
    + + + +
    + +
    + +
    +
  • +
+
+ +
+ + + +
+
+
+
diff --git a/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.scss b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.scss new file mode 100644 index 0000000..d6045ab --- /dev/null +++ b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.scss @@ -0,0 +1,124 @@ +fieldset +{ + height: 80vh; +} + +p.lead +{ + color: #ff9c07; + margin: 1rem 1rem 0; +} + +h4 +{ + color: #00dcff; +} + +h5 +{ + color: #ff9c07; +} + +.list-group +{ + counter-reset: count; +} + +.list-group-item +{ + background: none; + padding: .5rem .5rem .5rem 1rem; + border-radius: 0; + color: #5a8cd8; + border-color: rgb(61, 94, 142, .25); + counter-increment: count; + + .tag + { + padding: 0 1rem 0 2rem; + flex-grow: 1; + + &:before + { + content: counter(count) "."; + position: absolute; + top: 50%; + left: 1.5rem; + transform: translateY(-50%); + } + } + + .form-control + { + height: auto; + border-radius: 0; + background: none; + border-color: transparent; + color: #ff9c07; + font-size: 1.2rem; + padding: 0 .25rem; + + + .form-control + { + font-size: 1rem; + color: #8881ff; + + &:focus, &:active + { + color: #ff9c07; + } + } + + &:focus, &:active + { + background: none; + border-color: #00e7ff; + color: #ff9c07; + } + } +} + +textarea +{ + resize: vertical; +} + +.add-tag +{ + padding: 1rem; + + .form-control + { + background: none; + border-color: #00e7ff; + color: #ff9c07; + border-radius: 0; + border-radius: 3rem; + padding: .5rem 1rem; + + + .form-control + { + margin-top: .5rem; + border-radius: 2rem; + + + .btn + { + margin-top: .5rem; + padding: .5rem; + width: 100%; + } + } + } + + textarea + { + resize: none; + } +} + +.overflow-v +{ + overflow-y: auto; + overflow-x: hidden; + margin: 0 -1rem 1rem; +} diff --git a/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.spec.ts b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.spec.ts new file mode 100644 index 0000000..bf09982 --- /dev/null +++ b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceTagEditorComponent } from './instance-tag-editor.component'; + +describe('InstanceTagEditorComponent', () => { + let component: InstanceTagEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstanceTagEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceTagEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.ts b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.ts new file mode 100644 index 0000000..9d1e504 --- /dev/null +++ b/app/src/app/instances/instance-tag-editor/instance-tag-editor.component.ts @@ -0,0 +1,126 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { InstancesService } from '../helpers/instances.service'; +import { ToastrService } from 'ngx-toastr'; + +@Component({ + selector: 'app-instance-tag-editor', + templateUrl: './instance-tag-editor.component.html', + styleUrls: ['./instance-tag-editor.component.scss'] +}) +export class InstanceTagEditorComponent implements OnInit +{ + @Input() + instance: any; + + @Input() + showMetadata: boolean; + + save = new Subject<{ key: string; value: string }[]>(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + focus = 1; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly instancesService: InstancesService, + private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly toastr: ToastrService) + { + // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + const items = this.fb.array(this.showMetadata + ? Object.keys(this.instance.metadata).map(key => this.fb.group({ key, value: this.instance.metadata[key] })) + : Object.keys(this.instance.tags).map(key => this.fb.group({ key, value: this.instance.tags[key] })) + ); + + this.editorForm = this.fb.group({ + items, + key: [null, Validators.required], + value: [null, Validators.required] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + addTag() + { + const array = this.editorForm.get('items') as FormArray; + const key = this.editorForm.get('key').value; + const value = this.editorForm.get('value').value; + + if (array.controls.find(x => x.get('key').value === key)) + { + this.toastr.warning(`The key "${key}" is already present`); + return; + } + + array.push(this.fb.group({ key, value })); + + // Clear the form + this.editorForm.get('key').setValue(null); + this.editorForm.get('value').setValue(null); + + this.focus++; + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteTag(index: number) + { + const array = this.editorForm.get('items') as FormArray; + array.removeAt(index); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.save.next(); + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + const items = this.editorForm.getRawValue().items.reduce((map, item) => + { + map[item.key] = item.value; + return map; + }, {}); + + const observable = this.showMetadata + ? this.instancesService.replaceMetadata(this.instance.id, items) + : this.instancesService.replaceTags(this.instance.id, items); + + observable.subscribe(response => + { + this.save.next(response); + this.modalRef.hide(); + }, err => + { + this.toastr.error(err.error.message); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } +} diff --git a/app/src/app/instances/instance-wizard/instance-wizard.component.html b/app/src/app/instances/instance-wizard/instance-wizard.component.html new file mode 100644 index 0000000..1434daf --- /dev/null +++ b/app/src/app/instances/instance-wizard/instance-wizard.component.html @@ -0,0 +1,349 @@ +
+
+ + +
+
+
    +
  • + Create a new machine +
    +
  • + +
  • + {{ step.title }} + {{ step.selection.name }} +
    {{ step.description }}
    +
  • + +
  • +
+
+
+
+
+
+
+
+ Loading... +
+
+ +
+
+
Pick the type of machine you wish to create and the image used to provision it
+ +
+
+ +
+ +
+ +
+
+
+ + +
+
+
+
+ +
+
Choose the package that matches the technical specifications this machine will have
+ + + +
+ +
+
Give this machine a name for easier lookup
+ +
+
+
+ + +
+
+
+ +
+
+
Which networks will this machine use?
+
+
+
+
+ + +
+ +
+ + +
+
+
+
+
+ +
+
Enable the firewall for you machine
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+ + +
+
+ + + + + + + + + + + + + + + +
Volume nameMount pointRead only
+
+ + +
+
+ + +
+ +
+
+
+ +

+ + Volumes are disabled for KVM packages +

+
+
+ + +
+ + +
+
+
+
Place close to
+ +
+
+ + +
+ +
+ + +
+
+
+ +
+
Place far from
+ +
+
+ + +
+ +
+ + +
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+ + +
+
+
Tags make it easier to lookup an machine
+
+
+
+
+
+ {{ tag.value.key }}: + + {{ tag.value.value }} + + + +
+
+
+ + +
+
+
+ +
+
Metadata makes it easier to find a machine
+
+
+
+
+
+ {{ meta.value.key }}: + + {{ meta.value.value }} + + + +
+
+
+ +
+
+
+
+ +

+ You're about to create + an infrastructure container + a virtual machine + , + + {{ editorForm.get('package').value.description }} + + + having + {{ editorForm.get('package').value.vcpus || 1 }} vCPUs, + {{ editorForm.get('package').value.memory*1024*1024 | fileSize }} RAM and + {{ editorForm.get('package').value.disk*1024*1024 | fileSize }} storage + , + + named {{ editorForm.get('name').value }}, + + based on the {{ editorForm.get('image').value.description }} +

+
+ +
+ +
+ + + + + +
+
+
+
+
+
diff --git a/app/src/app/instances/instance-wizard/instance-wizard.component.scss b/app/src/app/instances/instance-wizard/instance-wizard.component.scss new file mode 100644 index 0000000..b3fcf83 --- /dev/null +++ b/app/src/app/instances/instance-wizard/instance-wizard.component.scss @@ -0,0 +1,338 @@ +fieldset +{ + height: 80vh; +} + +h5 +{ + padding-right: 1.5rem; +} + +h5, h6 +{ + color: #ff9c07; +} + +p +{ + color: #3d5e8e; +} + +.steps +{ + height: 100%; + + ul + { + display: flex; + flex-direction: column; + justify-content: space-around; + background: #0c1321; + color: #00dcff; + height: calc(100% - 4px); + margin: 2px 0 2px 2px; + border-radius: .35rem 0 0 .35rem; + counter-reset: step; + } + + li + { + background-color: transparent; + border: none; + font-size: 1.5rem; + color: #3d5e8e; + padding-right: .5rem; + font-family: "Bebas Neue", sans-serif; + position: relative; + margin: .5rem 0; + padding: 1rem .5rem 1rem 2.5rem; + + &:not(:first-child):not(:last-child) + { + counter-increment: step; + + &:after + { + left: .6rem; + top: 1.65rem; + content: counter(step); + height: 1.5rem; + width: 1.5rem; + position: absolute; + pointer-events: none; + border-radius: 50%; + font-size: 1rem; + padding: .2rem .4rem; + transform: translateY(-50%); + font-weight: bold; + border: 2px solid; + } + } + + &.active + { + color: #0bb13b; + + .step-description + { + opacity: .75; + } + } + } + + .step-description + { + font-size: .9rem; + font-family: "Mukta", sans-serif; + } + + .step-summary + { + color: #ff9c07; + margin: 0 .5rem 0 0; + } +} + +.btn +{ + transition: all .3s ease-in-out; +} + +.hidden +{ + opacity: 0; + pointer-events: none; +} + +.no-overflow +{ + overflow: hidden; +} + +.badge.rounded-pill +{ + padding: .1em .5em; + vertical-align: middle; +} + +.form-check:not(.form-switch) +{ + display: flex; + align-items: center; + padding: 0; + margin: 0 0 0 2.5rem; + cursor: pointer; + flex-grow: 1; + + .form-check-input + { + margin-right: .5rem; + float: none; + width: 1.4em; + max-width: 1rem; + margin-bottom: .25rem; + cursor: inherit; + background-color: #0dc3e9; + border-color: #0dc3e9; + box-shadow: 0 0 0 1px rgb(12, 19, 33, .5) inset; + + &:checked + { + background-color: #ff9c07; + border-color: #ff9c07; + + &:not(:focus) + { + box-shadow: none; + } + } + + &:checked[type=radio] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%230c1321'/%3e%3c/svg%3e"); + } + + &:checked[type=checkbox] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230c1321' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + } + + &:focus + { + box-shadow: 0 0 0 0.25rem rgba(255, 156, 7, .25); + } + } + + .h3 + { + text-transform: uppercase; + display: block; + margin-bottom: 0; + line-height: 1; + font-size: 1.5rem; + color: #8881ff; + } + + .form-check-label + { + cursor: inherit; + width: 100%; + padding: .75rem .25rem; + color: #5a8cd8; + font-family: "Mukta", sans-serif; + text-transform: none; + } +} + +.form-check-input:checked + .form-check-label, +.form-check-input:checked + .form-check-label .package-specs, +.form-check-input:checked + .form-check-label .h3 +{ + color: #ff9c07; +} + +.content +{ + border-radius: 0 .35rem .35rem 0; + padding: 1rem 0; + + .list-group:not(.select-list) + { + overflow: auto; + border-radius: 0; + border-top: 1px solid rgba(13, 195, 233, .5); + + .list-group-item + { + border-radius: 0; + background: transparent; + color: #5a8cd8; + border-color: rgba(61, 94, 142, .25); + + a + { + color: #0dc3e9; + } + } + } + + .btn-group + { + .btn + { + background: none; + border-radius: 0; + border-left: none; + border-right: none; + border-top: none; + border-bottom: 2px solid transparent; + transition: none; + color: #0dc3e9; + + &.active + { + border-bottom-color: #0dc3e9; + box-shadow: 0 -1rem 1.5rem -1.5rem inset; + text-shadow: 0 0 .5rem; + } + + &:focus:not(.active) + { + box-shadow: none; + } + } + } +} + +@media (max-width: 992px) +{ + .content + { + border-radius: .35rem; + } +} + +.select-list +{ + .form-check .form-check-label + { + padding: .5rem .25rem; + text-transform: uppercase; + font-family: "Bebas Neue", sans-serif; + + small + { + font-size: .85rem; + } + } + + .list-group-item + { + border-left: none; + border-right: none; + border-top: none; + border-radius: 0; + background: transparent; + color: #8881ff; + } +} + +.form-floating > .form-control, .form-floating > .form-select +{ + height: calc(3rem + 2px); + background: #0f1626; + border-color: #00e7ff; + border-radius: 3rem; +} + +.form-floating > .form-control +{ + color: #ff9c07; +} + +.image-type-selector +{ + color: #00e7ff; + text-transform: uppercase; + background: #0f1626; + border-color: #00e7ff; + border-radius: 3rem; +} + +.auto-height +{ + max-height: 100%; + overflow-x: hidden; + overflow-y: auto; +} + +p.lead b +{ + line-height: 1.4; +} + +.tag-value +{ + max-width: 340px; +} + +.table +{ + color: #5a8cd8; + + td + { + padding: 0 .125rem .25rem; + } + + th + { + padding: .25rem; + vertical-align: middle; + + &:first-child + { + padding-left: 1rem; + } + } +} diff --git a/app/src/app/instances/instance-wizard/instance-wizard.component.spec.ts b/app/src/app/instances/instance-wizard/instance-wizard.component.spec.ts new file mode 100644 index 0000000..04e6156 --- /dev/null +++ b/app/src/app/instances/instance-wizard/instance-wizard.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstanceWizardComponent } from './instance-wizard.component'; + +describe('InstanceWizardComponent', () => { + let component: InstanceWizardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InstanceWizardComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceWizardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instance-wizard/instance-wizard.component.ts b/app/src/app/instances/instance-wizard/instance-wizard.component.ts new file mode 100644 index 0000000..a3f938b --- /dev/null +++ b/app/src/app/instances/instance-wizard/instance-wizard.component.ts @@ -0,0 +1,574 @@ +import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { combineLatest, forkJoin, Subject } from 'rxjs'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray, ValidatorFn, ValidationErrors } from '@angular/forms'; +import { Instance } from '../models/instance'; +import { filter, takeUntil, startWith, distinctUntilChanged } from 'rxjs/operators'; +import { NavigationStart, Router } from '@angular/router'; +import { FileSizePipe } from '../../pipes/file-size.pipe'; +import { InstancesService } from '../helpers/instances.service'; +import { CatalogService } from '../../catalog/helpers/catalog.service'; +import { NetworkingService } from '../../networking/helpers/networking.service'; +import { ToastrService } from 'ngx-toastr'; +import { VolumesService } from '../../volumes/helpers/volumes.service'; +import { VolumeResponse } from '../../volumes/models/volume'; + +@Component({ + selector: 'app-instance-wizard', + templateUrl: './instance-wizard.component.html', + styleUrls: ['./instance-wizard.component.scss'] +}) +export class InstanceWizardComponent implements OnInit, OnDestroy +{ + @Input() + add: boolean; + + @Input() + imageType = 1; + + @Input() + instance: Instance; + + private images: any[]; + imageList: any[]; + operatingSystems: any[]; + + private packages: {}; + packageList: any[]; + packageGroups: any[]; + + instances: Instance[]; + dataCenters: any[]; + + loadingIndicator: boolean; + loadingPackages: boolean; + save = new Subject(); + working: boolean; + editorForm: FormGroup; + currentStep = 1; + steps: any[]; + preselectedPackage: string; + kvmRequired: boolean; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly fileSizePipe: FileSizePipe, + private readonly instancesService: InstancesService, + private readonly catalogService: CatalogService, + private readonly networkingService: NetworkingService, + private readonly volumesService: VolumesService, + private readonly toastr: ToastrService) + { + // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + + this.steps = [ + { + id: 1, + title: 'Image', + description: 'The image used to provision this machine' + }, + { + id: 2, + title: 'Package', + description: "This machine's technical specifications" + }, + { + id: 3, + title: 'Settings', + description: 'Name your machine, configure networks and setup volumes' + }, + { + id: 4, + title: 'Create', + description: 'Tag and create your machine' + } + ]; + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + const tags = this.fb.array(this.instance + ? Object.keys(this.instance.tags) + .map(key => this.fb.group({ key, value: this.instance.tags[key] })) + : []); + + const metadata = this.fb.array(this.instance + ? Object.keys(this.instance.metadata) + .filter(key => key !== 'root_authorized_keys') // This shouldn't be cloned + .map(key => this.fb.group({ key, value: this.instance.metadata[key] })) + : []); + + this.editorForm = this.fb.group( + { + imageType: [this.imageType], + imageOs: [], + image: [null, Validators.required], + package: [null, Validators.required], + name: [null, Validators.required], + networks: this.fb.array([], { validators: this.atLeastOneSelectionValidator.bind(this) }), + firewallRules: this.fb.array([]), + cloudFirewall: [this.instance?.firewall_enabled], + volumes: this.fb.array([]), + affinity: this.fb.group( + { + strict: [{ value: false, disabled: true }], + closeTo: [], + farFrom: [] + }), + dataCenter: [], + tags, + metadata + }); + + this.configureForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private configureForm() + { + this.editorForm.get('name').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(name => + { + this.steps[2].selection = { name }; + this.steps[2].complete = this.editorForm.get('name').valid && this.editorForm.get('networks').valid; + }); + + this.editorForm.get('networks').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(() => + { + this.steps[2].complete = this.editorForm.get('name').valid && this.editorForm.get('networks').valid; + }); + + this.editorForm.get('imageType').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => + { + const imageType = value | 0; + const imageList = []; + const operatingSystems = {}; + + if (imageType === 1) + { + for (const image of this.images) + if (['zvol'].includes(image.type)) + { + operatingSystems[image.os] = true; + imageList.push(image); + } + } + else if (imageType === 2) + { + for (const image of this.images) + if (['lx-dataset', 'zone-dataset'].includes(image.type)) + { + operatingSystems[image.os] = true; + imageList.push(image); + } + } + else if (imageType === 3) + { + for (const image of this.images) + if (!['zvol', 'lx-dataset', 'zone-dataset'].includes(image.type)) + { + operatingSystems[image.os] = true; + imageList.push(image); + } + } + + this.operatingSystems = Object.keys(operatingSystems); + + this.imageList = imageList.filter(x => x.os === this.operatingSystems[0]).sort((a, b) => a.name.localeCompare(b.name)); + + // Set the pre-selected operating system + this.editorForm.get('imageOs').setValue(this.operatingSystems[0]); + + // Invalidate previous selections + this.steps[0].selection = null; + this.steps[0].complete = true; + + this.editorForm.get('image').setValue(null); + }); + + this.editorForm.get('imageOs').valueChanges + .pipe(takeUntil(this.destroy$), distinctUntilChanged()) + .subscribe(imageOs => + { + const imageType = this.editorForm.get('imageType').value | 0; + const imageList = []; + const operatingSystems = {}; + + if (imageType === 1) + { + for (const image of this.images) + if (['zvol'].includes(image.type) && (!imageOs || imageOs === image.os)) + { + operatingSystems[image.os] = true; + imageList.push(image); + } + } + else if (imageType === 2) + { + for (const image of this.images) + if (['lx-dataset', 'zone-dataset'].includes(image.type) && (!imageOs || imageOs === image.os)) + { + operatingSystems[image.os] = true; + imageList.push(image); + } + } + else if (imageType === 3) + { + for (const image of this.images) + if (!['zvol', 'lx-dataset', 'zone-dataset'].includes(image.type) && (!imageOs || imageOs === image.os)) + { + operatingSystems[image.os] = true; + imageList.push(image); + } + } + + this.imageList = imageList.sort((a, b) => a.name.localeCompare(b.name)); + }); + + this.editorForm.get('image').valueChanges + .pipe(takeUntil(this.destroy$), distinctUntilChanged()) + .subscribe(x => + { + this.steps[0].selection = x; + this.steps[0].complete = !!x; + + this.kvmRequired = x?.requirements['brand'] === 'kvm' || x?.type === 'zvol' || false; + + this.loadingPackages = true; + }); + + this.editorForm.get('package').valueChanges + .pipe(takeUntil(this.destroy$), distinctUntilChanged()) + .subscribe(x => + { + this.steps[1].selection = x; + this.steps[1].complete = !!x; + + this.kvmRequired = this.editorForm.get('image').value?.requirements['brand'] === 'kvm' || + this.editorForm.get('image').value?.type === 'zvol' || + x?.brand === 'kvm' || false; + }); + + this.editorForm.get(['affinity', 'farFrom']).valueChanges.pipe(startWith(null)) + .pipe(takeUntil(this.destroy$)) + .subscribe(this.setAffinity.bind(this)); + + this.editorForm.get(['affinity', 'closeTo']).valueChanges.pipe(startWith(null)) + .pipe(takeUntil(this.destroy$)) + .subscribe(this.setAffinity.bind(this)); + } + + // ---------------------------------------------------------------------------------------------------------------- + private atLeastOneSelectionValidator: ValidatorFn = (array: FormArray): ValidationErrors | null => + { + let selected = 0; + + Object.keys(array.controls).forEach(key => + { + const control = array.controls[key]; + + if (control.value.selected) + selected++; + }); + + if (selected < 1) + { + return { atLeastOneSelection: true } + } + + return null; + } + + // ---------------------------------------------------------------------------------------------------------------- + private setAffinity(affinity) + { + if (affinity) + this.editorForm.get(['affinity', 'strict']).enable(); + else + this.editorForm.get(['affinity', 'strict']).disable(); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + + const changes = this.editorForm.getRawValue(); + + const instance = new Instance(); + instance.name = changes.name; + instance.image = changes.image.id; + instance.package = changes.package.id; + //instance.brand = changes.package.brand; + instance.networks = changes.networks.filter(x => x.selected).map(x => x.id); + instance.firewall_enabled = !!changes.cloudFirewall; + + if (!this.kvmRequired) + instance.volumes = changes.volumes + .filter(x => x.mount) + .map(volume => + ({ + name: volume.name, + type: 'tritonnfs', + mode: volume.ro ? 'ro' : 'rw', + mountpoint: volume.mountpoint + })); + + console.log(changes, instance); + + this.instancesService.add(instance) + .subscribe(x => + { + this.working = false; + + const imageDetails = this.images.find(i => i.id === x.image); + if (imageDetails) + x.imageDetails = imageDetails[0]; + + this.save.next(x); + this.modalRef.hide(); + }, err => + { + this.toastr.error(err.error.message); + this.working = false; + }); + + } + + // ---------------------------------------------------------------------------------------------------------------- + previousStep() + { + this.currentStep = this.currentStep > 1 ? this.currentStep - 1 : 1; + } + + // ---------------------------------------------------------------------------------------------------------------- + nextStep() + { + this.currentStep = this.currentStep < this.steps.length ? this.currentStep + 1 : this.steps.length; + } + + // ---------------------------------------------------------------------------------------------------------------- + setPackage(selection: any) + { + this.steps[1].selection = selection; + this.steps[1].complete = true; + + this.editorForm.get('package').setValue(selection); + + if (this.instance) + this.nextStep(); + } + + // ---------------------------------------------------------------------------------------------------------------- + addTag(event) + { + const array = this.editorForm.get('tags') as FormArray; + + array.push(this.fb.group({ + key: event.key, + value: event.value + })); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeTag(index) + { + const array = this.editorForm.get('tags') as FormArray; + + array.removeAt(index); + } + + // ---------------------------------------------------------------------------------------------------------------- + addMetadata(event) + { + const array = this.editorForm.get('metadata') as FormArray; + + array.push(this.fb.group({ + key: event.key, + value: event.value + })); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeMetadata(index) + { + const array = this.editorForm.get('metadata') as FormArray; + + array.removeAt(index); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getImages() + { + this.loadingIndicator = true; + + this.catalogService.getImages() + .subscribe(response => + { + this.images = response; + + // Set the default image type (this will trigger a series of events) + this.editorForm.get('imageType').setValue(this.imageType); + + if (this.instance) + { + const image = this.images.find(x => x.id === this.instance.image); + this.editorForm.get('image').setValue(image); + } + + this.loadingIndicator = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getNetworksAndFirewallRules() + { + const networks = this.editorForm.get('networks') as FormArray; + // const firewallRules = this.editorForm.get('firewallRules') as FormArray; + + if (networks.length) + return; + + forkJoin( + [ + this.networkingService.getNetworks(), + //this.networkingService.getFirewallRules() + ] + ).subscribe(response => + { + for (const network of response[0]) + networks.push(this.fb.group({ + id: [network.id], + name: [network.name], + description: [network.description], + public: [network.public], + selected: [false] + })); + + // for (const firewallRule of response[1]) + // firewallRules.push(this.fb.group({ + // id: [firewallRule.id], + // name: [firewallRule.name], + // description: [firewallRule.description], + // selected: [false] + // })); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getInstancesAndDataCenters() + { + if (this.instances || this.dataCenters) + return; + + forkJoin( + this.instancesService.get(), + this.catalogService.getDataCenters() + ) + .subscribe(response => + { + this.instances = response[0]; + + this.dataCenters = Object.keys(response[1]); + + this.editorForm.get('dataCenter').setValue(this.dataCenters[0]); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getVolumes() + { + this.volumesService.getVolumes() + .subscribe(volumes => + { + const array = this.editorForm.get('volumes') as FormArray; + + for (const volume of volumes) + { + const control = this.fb.group( + { + mount: [false], + name: [{ value: volume.name, disabled: true }, Validators.required], + ro: [{ value: false, disabled: true }], + mountpoint: [{ value: '', disabled: true }, [Validators.required, Validators.minLength(2)]] + }); + + control.get('mount').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(mount => + { + if (mount) + { + control.get('ro').enable(); + control.get('mountpoint').enable(); + } + else + { + control.get('ro').disable(); + control.get('mountpoint').disable(); + } + }); + + array.push(control); + } + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + if (this.imageType < 1 || this.imageType > 4) + throw 'Invalid image type'; + + this.createForm(); + + this.getNetworksAndFirewallRules(); + + this.getInstancesAndDataCenters(); + + this.getVolumes(); + + if (this.instance) + { + if (this.instance.type === 'virtualmachine') + this.imageType = 1; + else if (this.instance.type === 'smartmachine') + this.imageType = 2; + + this.preselectedPackage = this.instance.package; + + this.nextStep(); + } + + if (this.imageType <= 2) + this.getImages(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy(): void + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instances.component.html b/app/src/app/instances/instances.component.html new file mode 100644 index 0000000..0a55524 --- /dev/null +++ b/app/src/app/instances/instances.component.html @@ -0,0 +1,339 @@ +
+
+
+
+ +
+ + + + +
+ + +
+ +
+ +
+ +
+ + +
+
+
+ +
+ Loading... +
+
+ +
+
+

+ No machine matches your filters +

+ + +
+
+
+
+
+
+ {{ instance.name }} +
+ +
+ {{ instance.imageDetails.name }}, v{{ instance.imageDetails.version }} +
+ + +
+ +
+
+ Working... +
+
+ +
+
+ + + {{ instance.brand }} - infrastructure container + + + + {{ instance.brand }} - virtual machine + +
+ +
+ + {{ instance.state }} + + +
+ + + + + +
+
+
+
+ +
+
+ + + + + Info + +
+ + +
+
+ + + + Network + +
+ + +
+
+ + + + + Volumes + +
+
    +
  • +
    + + + {{ volume.name }} + + + {{ volume.size * 1024 * 1024 | fileSize }} +
    +
  • +
+
+
+ + + + Snapshots + +
+ + +
+
+ + + + Migrations + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ + + + + + + + + + + + + + +
+ + +
+
+
+ + + + diff --git a/app/src/app/instances/instances.component.scss b/app/src/app/instances/instances.component.scss new file mode 100644 index 0000000..300efb6 --- /dev/null +++ b/app/src/app/instances/instances.component.scss @@ -0,0 +1,152 @@ +.ips +{ + + .ips + { + margin-left: .5rem; + padding-left: .5rem; + border-left: 2px solid #2b3540; + } +} + +.card +{ + border: 1px solid rgba(0, 0, 0, .5); + background-color: rgba(16, 21, 39, .5); + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + transition: box-shadow .15s ease-out; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgba(18, 203, 240, .4), 0 0 10px 3px #0e162a; + } + + .card-title + { + color: #ff9c07; + margin-bottom: 0; + font-weight: bold; + } + + .card-body + { + background-color: rgba(16, 21, 39, .5); + max-height: 205px; + overflow: auto; + padding-top: .7rem !important; + } + + .list-group-item + { + background: none; + + span + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: bold; + } + } + + .btn-info, .btn-outline-info + { + font-size: 1rem; + line-height: 1; + } +} + +.card-info +{ + background-color: rgba(16, 21, 39, .75); + display: flex; + flex-direction: column; + justify-content: space-between; + padding: .25rem .5rem .5rem; + height: 170px; +} + +.full-details .card-info +{ + height: 240px; +} + +@media (max-width: 576px) +{ + .card-info + { + height: auto; + } +} + +.no-overflow-sm +{ + overflow: hidden; +} + +@media (max-width: 992px) +{ + .no-overflow-sm + { + overflow: visible; + } +} + +.open .dropdown-toggle +{ +} + +.filters +{ + width: 240px; +} + +.form-check-label +{ + color: inherit; +} + +.btn-toolbar .btn +{ + .badge + { + padding: 0.1rem 0.35rem 0; + border: 1px solid transparent; + text-shadow: 0 0 3px rgb(255 255 255 / 25%); + + &:first-letter + { + font-size: 1.2rem; + } + } + + &:hover + { + .badge + { + border-color: #000; + } + } +} + +@keyframes flash +{ + from, 50%, to + { + opacity: 1; + } + + 25%, 75% + { + opacity: 0; + } +} + +.flash +{ + animation-name: flash; + animation-duration: calc(.9s * 1.3); + animation-timing-function: ease-in-out; +} diff --git a/app/src/app/instances/instances.component.spec.ts b/app/src/app/instances/instances.component.spec.ts new file mode 100644 index 0000000..6782877 --- /dev/null +++ b/app/src/app/instances/instances.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstancesComponent } from './instances.component'; + +describe('InstancesComponent', () => { + let component: InstancesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InstancesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InstancesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/instances.component.ts b/app/src/app/instances/instances.component.ts new file mode 100644 index 0000000..f8e2304 --- /dev/null +++ b/app/src/app/instances/instances.component.ts @@ -0,0 +1,817 @@ +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { InstancesService } from './helpers/instances.service'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { debounceTime, delay, distinctUntilChanged, first, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { InstanceWizardComponent } from './instance-wizard/instance-wizard.component'; +import { SelectionType, ColumnMode } from '@swimlane/ngx-datatable'; +import { Instance } from './models/instance'; +import { forkJoin, Subject } from 'rxjs'; +import { ToastrService } from 'ngx-toastr'; +import { CatalogService } from '../catalog/helpers/catalog.service'; +import { PackageSelectorComponent } from './package-selector/package-selector.component'; +import { PromptDialogComponent } from '../components/prompt-dialog/prompt-dialog.component'; +import { InstanceTagEditorComponent } from './instance-tag-editor/instance-tag-editor.component'; +import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component'; +import { InstanceHistoryComponent } from './instance-history/instance-history.component'; +import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component'; +import { VirtualScrollerComponent } from 'ngx-virtual-scroller'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import Fuse from 'fuse.js'; +import { LabelType, Options } from '@angular-slider/ngx-slider'; +import { FileSizePipe } from '../pipes/file-size.pipe'; +import { sortArray } from '../helpers/utils.service'; +import { VolumesService } from '../volumes/helpers/volumes.service'; + +@Component({ + selector: 'app-instances', + templateUrl: './instances.component.html', + styleUrls: ['./instances.component.scss'] +}) +export class InstancesComponent implements OnInit, OnDestroy +{ + @ViewChild(VirtualScrollerComponent) + private virtualScroller: VirtualScrollerComponent; + + loadingIndicator = true; + instances: Instance[] = []; + listItems: Instance[]; + images = []; + packages = []; + volumes = []; + lazyLoadDelay: number; + canPrepareForLoading: boolean; + editorForm: FormGroup; + showMachineDetails: boolean; + runningInstanceCount = 0; + stoppedInstanceCount = 0; + instanceStateArray: string[] = []; + memoryFilterOptions: Options = { + animate: false, + stepsArray: [], + draggableRange: true, + showTicks: true, + translate: this.translateBytes.bind(this) + }; + diskFilterOptions: Options = { + animate: false, + stepsArray: [], + draggableRange: true, + showTicks: true, + translate: this.translateBytes.bind(this) + }; + + private destroy$ = new Subject(); + private stableStates = ['running', 'stopped']; + private minimumLazyLoadDelay = 1000; + private readonly fuseJsOptions: {}; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly instancesService: InstancesService, + private readonly catalogService: CatalogService, + private readonly volumesService: VolumesService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService, + private readonly fb: FormBuilder, + private readonly fileSizePipe: FileSizePipe) + { + this.lazyLoadDelay = this.minimumLazyLoadDelay; + + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: true, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'name', weight: .9 }, + { name: 'metadataKeys', weight: .7 }, + { name: 'tagKeys', weight: .7 }, + { name: 'os', weight: .5 }, + { name: 'brand', weight: .5 } + ] + }; + + this.showMachineDetails = !!JSON.parse(localStorage.getItem('showMachineDetails') || '0'); + + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private translateBytes(value: number, label: LabelType): string + { + const formattedValue = this.fileSizePipe.transform(value * 1024 * 1024); + + if (this.instances.length === 1) + return formattedValue; + + switch (label) + { + case LabelType.Low: + return `Between ${formattedValue}`; + case LabelType.High: + return `and ${formattedValue}`; + default: + return formattedValue; + } + } + + // ---------------------------------------------------------------------------------------------------------------- + private getInstances() + { + this.instancesService.get() + .subscribe(instances => + { + //// DEMO ONLY !!!!! + //const arr = new Array(200); + //for (let j = 0; j < 200; j++) + //{ + // const el = { ...instances[0] }; + // el.name = this.dummyNames[j]; + // arr[j] = el; + //}/**/ + //// DEMO ONLY !!!!! + + this.instances = instances.map(instance => + { + instance.metadataKeys = Object.keys(instance.metadata); + instance.tagKeys = Object.keys(instance.tags); + + instance.loading = true; // Required for improved scrolling experience + return instance; + }); + + this.getImagesPackagesAndVolumes(); + + this.computeFiltersOptions(); + + this.applyFiltersAndSort(); + + this.loadingIndicator = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getImagesPackagesAndVolumes() + { + forkJoin({ + images: this.catalogService.getImages(), + packages: this.catalogService.getPackages(), + volumes: this.volumesService.getVolumes() + }) + .subscribe(response => + { + this.images = response.images; + this.packages = response.packages; + this.volumes = response.volumes; + + for (const instance of this.instances) + this.fillInInstanceDetails(instance); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + searchTerm: [''], + sortProperty: ['name'], + filters: this.fb.group( + { + stateFilter: [], + brandFilter: [], + typeFilter: [], + memoryFilter: [[0, 0]], + diskFilter: [[0, 0]], + imageFilter: [], // instances provisioned with a certain image + }), + filtersActive: [false], + showMachineDetails: [this.showMachineDetails] + }); + + this.editorForm.get('searchTerm').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('sortProperty').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('filters').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('showMachineDetails').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(showMachineDetails => + { + this.editorForm.get('showMachineDetails').disable(); + + // Performance hack + setTimeout(() => this.showMachineDetails = !!showMachineDetails, 0); + + this.updateList(); + + // Store this setting in the local storage + localStorage.setItem('showMachineDetails', JSON.stringify(showMachineDetails)); + + setTimeout(() => this.editorForm.get('showMachineDetails').enable(), 300); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private applyFiltersAndSort() + { + let listItems: Instance[] = null; + + const searchTerm = this.editorForm.get('searchTerm').value; + if (searchTerm.length >= 2) + { + const fuse = new Fuse(this.instances, this.fuseJsOptions); + const fuseResults = fuse.search(searchTerm); + listItems = fuseResults.map(x => x.item); + } + + if (!listItems) + listItems = [...this.instances]; + + const stateFilter = this.editorForm.get(['filters', 'stateFilter']).value; + if (stateFilter) + { + listItems = listItems.filter(x => x.state === stateFilter); + this.editorForm.get('filtersActive').setValue(true); + } + + const memoryFilter = this.editorForm.get(['filters', 'memoryFilter']).value; + if (memoryFilter.every(x => !!x)) + { + listItems = listItems.filter(x => x.memory >= memoryFilter[0] && x.memory <= memoryFilter[1]); + //this.editorForm.get('filtersActive').setValue(true); + } + + const diskFilter = this.editorForm.get(['filters', 'diskFilter']).value; + if (memoryFilter.every(x => !!x)) + { + listItems = listItems.filter(x => x.disk >= diskFilter[0] && x.disk <= diskFilter[1]); + //this.editorForm.get('filtersActive').setValue(true); + } + + this.listItems = sortArray(listItems, this.editorForm.get('sortProperty').value); + } + + // ---------------------------------------------------------------------------------------------------------------- + clearFilters() + { + this.editorForm.get('filters').reset(); + this.editorForm.get('filtersActive').setValue(false); + + this.computeFiltersOptions(); + } + + // ---------------------------------------------------------------------------------------------------------------- + setSortProperty(propertyName: string) + { + this.editorForm.get('sortProperty').setValue(propertyName); + } + + // ---------------------------------------------------------------------------------------------------------------- + clearSearch() + { + this.editorForm.get('searchTerm').setValue(''); + } + + // ---------------------------------------------------------------------------------------------------------------- + updateList() + { + this.virtualScroller.refresh(); + } + + // ---------------------------------------------------------------------------------------------------------------- + prepareForLoading(instances: Instance[]) + { + for (const instance of instances) + instance.loading = true; + + return instances; + } + + // ---------------------------------------------------------------------------------------------------------------- + trackByFunction = (index: number, instance: Instance) => instance.name; + + // ---------------------------------------------------------------------------------------------------------------- + private computeFiltersOptions(computeOnlyState = false) + { + this.runningInstanceCount = 0; + this.stoppedInstanceCount = 0; + this.instanceStateArray = []; + + const memoryValues = {}; + const diskValues = {}; + + for (const instance of this.instances) + { + if (instance.state === 'running') + this.runningInstanceCount++; + + if (instance.state === 'stopped') + this.stoppedInstanceCount++; + + if (!~this.instanceStateArray.indexOf(instance.state)) + this.instanceStateArray.push(instance.state); + + if (!computeOnlyState && !memoryValues[instance.memory]) + memoryValues[instance.memory] = true; + + if (!computeOnlyState && !diskValues[instance.disk]) + diskValues[instance.disk] = true; + } + + if (computeOnlyState) + return; + + const memoryValuesArray = Object.keys(memoryValues); + this.memoryFilterOptions.stepsArray = memoryValuesArray.map(x => ({ legend: '', value: parseInt(x) })); + if (this.memoryFilterOptions.stepsArray.length) + this.editorForm.get(['filters', 'memoryFilter']).setValue([ + this.memoryFilterOptions.stepsArray[0].value, + this.memoryFilterOptions.stepsArray[this.memoryFilterOptions.stepsArray.length - 1].value + ]); + + const diskValuesArray = Object.keys(diskValues); + this.diskFilterOptions.stepsArray = diskValuesArray.map(x => ({ legend: '', value: parseInt(x) })); + if (this.diskFilterOptions.stepsArray.length) + this.editorForm.get(['filters', 'diskFilter']).setValue([ + this.diskFilterOptions.stepsArray[0].value, + this.diskFilterOptions.stepsArray[this.diskFilterOptions.stepsArray.length - 1].value + ]); + } + + // ---------------------------------------------------------------------------------------------------------------- + startMachine(instance: Instance) + { + if (instance.state !== 'stopped') + return; + + instance.working = true; + this.toastr.info(`Starting machine "${instance.name}"...`); + + this.instancesService.start(instance.id) + .pipe( + delay(1000), + switchMap(() => + this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x => + { + instance.state = x.state; + + this.computeFiltersOptions(); + }) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(() => + { + this.computeFiltersOptions(); + + instance.working = false; + this.toastr.info(`The machine "${instance.name}" has been started`); + }, err => + { + this.computeFiltersOptions(); + + instance.working = false; + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + restartMachine(instance: Instance) + { + if (instance.state !== 'running') + return; + + instance.working = true; + this.toastr.info(`Restarting machine "${instance.name}"...`); + + this.instancesService.reboot(instance.id) + .pipe( + delay(1000), + switchMap(() => this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x => + { + instance.state = x.state; + + this.computeFiltersOptions(); + }) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(() => + { + this.computeFiltersOptions(); + + instance.working = false; + + this.toastr.info(`The machine "${instance.name}" has been restarted`); + }, err => + { + this.computeFiltersOptions(); + + instance.working = false; + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + stopMachine(instance: Instance) + { + if (instance.state !== 'running') + return; + + instance.working = true; + this.toastr.info(`Stopping machine "${instance.name}"`); + + this.instancesService.stop(instance.id) + .pipe( + delay(1000), + switchMap(() => this.instancesService.getInstanceUntilExpectedState(instance, ['stopped'], x => + { + instance.state = x.state; + + this.computeFiltersOptions(); + }) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(() => + { + this.computeFiltersOptions(); + + instance.working = false; + this.toastr.info(`The machine "${instance.name}" has been stopped`); + }, err => + { + this.computeFiltersOptions(); + + instance.working = false; + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + resizeMachine(instance: Instance) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { instance } + }; + + const modalRef = this.modalService.show(PackageSelectorComponent, modalConfig); + modalRef.setClass('modal-lg'); + + modalRef.content.save + .pipe( + tap(() => + { + this.toastr.info(`Changing specifications for machine "${instance.name}"...`); + instance.working = true; + }), + first(), + switchMap(pkg => this.instancesService.resize(instance.id, pkg.id).pipe(map(() => pkg))) + ) + .subscribe(pkg => + { + instance.package = pkg.name; + instance.memory = pkg.memory; + instance.disk = pkg.disk; + + this.fillInInstanceDetails(instance); + + this.computeFiltersOptions(); + + instance.working = false; + this.toastr.info(`The specifications for machine "${instance.name}" have been changed`); + }, err => + { + instance.working = false; + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Couldn't change the specifications for machine "${instance.name}" ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + renameMachine(instance: Instance) + { + const instanceName = instance.name; + + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + value: instanceName, + required: true, + title: 'Rename machine', + prompt: 'Type in the new name for your machine', + placeholder: 'New machine name', + saveButtonText: 'Change machine name' + } + }; + + const modalRef = this.modalService.show(PromptDialogComponent, modalConfig); + + modalRef.content.save.pipe(first()).subscribe(name => + { + if (name === instanceName) + { + this.toastr.warning(`You provided the same name for machine "${instanceName}"`); + return; + } + + instance.working = true; + + this.instancesService.rename(instance.id, name) + .subscribe(() => + { + instance.name = name; + + + this.applyFiltersAndSort(); + + this.toastr.info(`The "${instanceName}" machine has been renamed to "${instance.name}"`); + instance.working = false; + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Couldn't rename the "${instanceName}" machine ${errorDetails}`); + instance.working = false; + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + showTagEditor(instance: Instance, showMetadata = false) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { instance, showMetadata } + }; + + const modalRef = this.modalService.show(InstanceTagEditorComponent, modalConfig); + modalRef.setClass('modal-lg'); + + modalRef.content.save.pipe(first()).subscribe(x => + { + // TODO: Refresh list + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + createImageFromMachine(instance: Instance) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { instance } + }; + + const modalRef = this.modalService.show(CustomImageEditorComponent, modalConfig); + + modalRef.content.save.pipe(first()).subscribe(x => + { + this.toastr.info(`Creating a new image based on the "${instance.name}" machine...`); + + this.catalogService.createImage(instance.id, x.name, x.version, x.description) + .pipe( + delay(1000), + switchMap(image => this.catalogService.getImageUntilExpectedState(image, ['active', 'failed']) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(image => + { + if (image.state === 'active') + this.toastr.info(`A new image "${x.name}" based on the "${instance.name}" machine has been created`); + else + this.toastr.error(`Failed to create an image based on the "${instance.name}" machine`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to create an image based on the "${instance.name}" machine ${errorDetails}`); + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + createMachine(instance?: Instance) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { instance } + }; + + const modalRef = this.modalService.show(InstanceWizardComponent, modalConfig); + modalRef.setClass('modal-xl'); + + modalRef.content.save.pipe(first()).subscribe(x => + { + if (!x) return; + + this.fillInInstanceDetails(x); + + this.instances.push(x); + + this.computeFiltersOptions(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteMachine(instance: Instance) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${instance.name}" machine?`, + confirmButtonText: 'Yes, delete this machine', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe(first()).subscribe(() => + { + instance.working = true; + + this.toastr.info(`Removing machine "${instance.name}"...`); + + this.instancesService.delete(instance.id) + .subscribe(() => + { + const index = this.instances.findIndex(i => i.id === instance.id); + if (index >= 0) + this.instances.splice(index, 1); + + this.computeFiltersOptions(); + + this.toastr.info(`The machine "${instance.name}" has been removed`); + }, + err => + { + instance.working = false; + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + showMachineHistory(instance: Instance) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { instance } + }; + + const modalRef = this.modalService.show(InstanceHistoryComponent, modalConfig); + } + + // ---------------------------------------------------------------------------------------------------------------- + tabChanged(event, instance: Instance) + { + if (event.id.endsWith('info')) + instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value; + else if (event.id.endsWith('snapshots')) + instance.shouldLoadSnapshots = this.editorForm.get('showMachineDetails').value; + else if (event.id.endsWith('networks')) + instance.shouldLoadNetworks = this.editorForm.get('showMachineDetails').value; + else if (event.id.endsWith('firewall')) + instance.shouldLoadFirewallRules = this.editorForm.get('showMachineDetails').value; + else if (event.id.endsWith('volumes')) + { + //instance.shouldLoadVolumes = this.editorForm.get('showMachineDetails').value; + } + else if (event.id.endsWith('migrations')) + { + //instance.shouldLoadMigrations = this.editorForm.get('showMachineDetails').value; + } + } + + // ---------------------------------------------------------------------------------------------------------------- + loadInstanceDetails(instance: Instance): any + { + instance.loading = false; + + instance.working = !this.stableStates.includes(instance.state); + + // Keep polling the instances that are not in a "stable" state + if (instance.working) + this.instancesService.getInstanceUntilExpectedState(instance, this.stableStates, x => + { + instance.state = x.state; + + this.computeFiltersOptions(true); + }) + .pipe(takeUntil(this.destroy$)) + .subscribe(x => + { + instance.working = false; + + // Update the instance with what we got from the server + const index = this.instances.findIndex(i => i.id === instance.id); + if (index >= 0) + this.instances.splice(index, 1, x); + }, err => + { + this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`); + instance.working = false; + }); + + instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value; + } + + // ---------------------------------------------------------------------------------------------------------------- + watchInstanceState(instance: Instance) + { + instance.working = true; + + this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x => + { + instance.state = x.state; + + this.computeFiltersOptions(true); + }) + .pipe( + delay(3000), + takeUntil(this.destroy$) + ).subscribe(() => { }, err => + { + instance.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + updateInstance(instance: Instance, updates: Instance) + { + instance.state = updates.state; + } + + // ---------------------------------------------------------------------------------------------------------------- + private fillInInstanceDetails(instance: Instance) + { + const imageDetails = this.images.find(i => i.id === instance.image); + if (imageDetails) + instance.imageDetails = imageDetails; + + const packageDetails = this.packages.find(p => p.name === instance.package); + if (packageDetails) + instance.packageDetails = packageDetails; + + instance.volumes = this.volumes.filter(i => i.refs && i.refs.includes(instance.id)); + } + + // ---------------------------------------------------------------------------------------------------------------- + private randomIntFromInterval(min, max) + { + // min and max included + return Math.floor(Math.random() * (max - min + 1) + min); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.getInstances(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/instances/instances.module.ts b/app/src/app/instances/instances.module.ts new file mode 100644 index 0000000..e0fbaac --- /dev/null +++ b/app/src/app/instances/instances.module.ts @@ -0,0 +1,91 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { TranslateLoader } from '@ngx-translate/core'; +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { TranslateCompiler } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { InstancesComponent } from './instances.component'; +import { InstanceWizardComponent } from './instance-wizard/instance-wizard.component'; +import { PackageSelectorComponent } from './package-selector/package-selector.component'; +import { InstanceSnapshotsComponent } from './instance-snapshots/instance-snapshots.component'; +import { InstanceNetworksComponent } from './instance-networks/instance-networks.component'; +import { InstanceSecurityComponent } from './instance-security/instance-security.component'; +import { InstanceTagEditorComponent } from './instance-tag-editor/instance-tag-editor.component'; +import { InstanceHistoryComponent } from './instance-history/instance-history.component'; +import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component'; +import { InstanceInfoComponent } from './instance-info/instance-info.component'; + +@NgModule({ + declarations: [ + InstancesComponent, + InstanceWizardComponent, + PackageSelectorComponent, + InstanceSnapshotsComponent, + InstanceNetworksComponent, + InstanceSecurityComponent, + InstanceTagEditorComponent, + InstanceHistoryComponent, + InstanceInfoComponent, + ], + imports: [ + SharedModule, + RouterModule.forChild([ + { + path: '', + component: InstancesComponent, + data: + { + title: 'instances.title', + subTitle: 'instances.subTitle', + icon: 'server' + }, + children: [ + { + path: 'wizard', + component: InstanceWizardComponent, + data: + { + title: 'instances.wizard.title', + subTitle: 'instances.wizard.subTitle', + icon: 'hat-wizard' + } + } + ] + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('dashboard') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ], + entryComponents: [ + InstanceWizardComponent, + PackageSelectorComponent, + InstanceTagEditorComponent, + InstanceHistoryComponent, + CustomImageEditorComponent + + ] +}) +export class InstancesModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/app/instances/models/instance-disk.ts b/app/src/app/instances/models/instance-disk.ts new file mode 100644 index 0000000..e7b3782 --- /dev/null +++ b/app/src/app/instances/models/instance-disk.ts @@ -0,0 +1,7 @@ +export class InstanceDisk +{ + id: string; + boot: boolean; + image: string; + size: number; +} diff --git a/app/src/app/instances/models/instance-volume.ts b/app/src/app/instances/models/instance-volume.ts new file mode 100644 index 0000000..c849fe1 --- /dev/null +++ b/app/src/app/instances/models/instance-volume.ts @@ -0,0 +1,7 @@ +export class InstanceVolume +{ + name: string; + type: string; // "tritonnfs" + mode: string; // "ro" / "rw" + mountpoint: string; // must start with a "/" +} diff --git a/app/src/app/instances/models/instance.ts b/app/src/app/instances/models/instance.ts new file mode 100644 index 0000000..d373ea9 --- /dev/null +++ b/app/src/app/instances/models/instance.ts @@ -0,0 +1,50 @@ +import { Network } from '../../networking/models/network'; +import { InstanceDisk } from './instance-disk'; +import { Nic } from './nic'; +import { InstanceVolume } from './instance-volume'; +import { CatalogImage } from '../../catalog/models/image'; +import { CatalogPackage } from '../../catalog/models/package'; + +export class InstanceRequest +{ + id: string; + name: string; + package: string; + image: string; + networks: Network[]; + tags: { key: string; value: string }; + metadata: { key: string; value: string }; + affinity: any[]; // Optional + brand: string; + firewall_enabled: boolean; + deletion_protection: boolean; + allow_shared_images: boolean; // Whether to allow provisioning from a shared image. + volumes: InstanceVolume[]; // list of objects representing volumes to mount when the newly created machine boots + disks: InstanceDisk[]; // An array of disk objects to be created (bhyve) + disk: number; + encrypted: boolean; // Place this instance into an encrypted server. Optional. + visible: boolean; +} + +export class Instance extends InstanceRequest +{ + nics: Nic[]; + imageDetails: CatalogImage; + packageDetails: CatalogPackage; + dns_names: string[]; + dnsList: {}; + memory: number; + type: string; + state: string; + + loading: boolean; + working: boolean; + shouldLoadInfo: boolean; + shouldLoadNetworks: boolean; + shouldLoadSecurity: boolean; + shouldLoadSnapshots: boolean; + shouldLoadFirewallRules: boolean; + volumesEnabled: boolean; + metadataKeys: string[]; + tagKeys: string[]; +} diff --git a/app/src/app/instances/models/nic.ts b/app/src/app/instances/models/nic.ts new file mode 100644 index 0000000..7c37471 --- /dev/null +++ b/app/src/app/instances/models/nic.ts @@ -0,0 +1,14 @@ +import { Network } from '../../networking/models/network'; + +export class Nic +{ + name: string; + mac: string; + ip: string; + gateway: string; + network: string; + networkDetails: Network; + networkName: string; + state: string; + primary: boolean; +} diff --git a/app/src/app/instances/models/snapshot.ts b/app/src/app/instances/models/snapshot.ts new file mode 100644 index 0000000..eabd160 --- /dev/null +++ b/app/src/app/instances/models/snapshot.ts @@ -0,0 +1,9 @@ +export class Snapshot +{ + name: string; + created: Date; + updated: Date; + size: number; + state: string; + working: boolean; +} diff --git a/app/src/app/instances/package-selector/package-selector.component.html b/app/src/app/instances/package-selector/package-selector.component.html new file mode 100644 index 0000000..5e51b38 --- /dev/null +++ b/app/src/app/instances/package-selector/package-selector.component.html @@ -0,0 +1,28 @@ +
+
+ + +
+

Change machine specifications

+ +

Pick the package that best suits your requirements

+ + + +
+ This machine's current package is: {{ instance.package }} +
+ +

+ This package you've selected is: {{ editorForm.get('package').value.name }} +

+ +
+ + +
+
+
+
diff --git a/app/src/app/instances/package-selector/package-selector.component.scss b/app/src/app/instances/package-selector/package-selector.component.scss new file mode 100644 index 0000000..97e187c --- /dev/null +++ b/app/src/app/instances/package-selector/package-selector.component.scss @@ -0,0 +1,33 @@ +fieldset +{ + height: 80vh; +} + +.content +{ + display: flex; + flex-direction: column; +} + +app-packages +{ + margin: 0 -1rem; +} + +p +{ + color: #ff9c07; + font-size: 1.15rem; +} + +.current-package +{ + color: #3d5e8e; + margin: 1rem 0 0; +} + +.selected-package +{ + margin: .5rem 0 0; + font-size: 1rem; +} diff --git a/app/src/app/instances/package-selector/package-selector.component.spec.ts b/app/src/app/instances/package-selector/package-selector.component.spec.ts new file mode 100644 index 0000000..2a6f3fc --- /dev/null +++ b/app/src/app/instances/package-selector/package-selector.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PackageSelectorComponent } from './package-selector.component'; + +describe('PackageSelectorComponent', () => { + let component: PackageSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PackageSelectorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PackageSelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/instances/package-selector/package-selector.component.ts b/app/src/app/instances/package-selector/package-selector.component.ts new file mode 100644 index 0000000..2471d36 --- /dev/null +++ b/app/src/app/instances/package-selector/package-selector.component.ts @@ -0,0 +1,83 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { CatalogPackage } from '../../catalog/models/package'; + +@Component({ + selector: 'app-package-selector', + templateUrl: './package-selector.component.html', + styleUrls: ['./package-selector.component.scss'] +}) +export class PackageSelectorComponent implements OnInit +{ + @Input() + instance: any; + + save = new Subject(); + imageType: number; + working: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + package: [null, Validators.required] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.save.next(this.editorForm.get('package').value); + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + packageSelected(pkg: any) + { + this.editorForm.get('package').setValue(pkg); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + switch (this.instance.type) + { + case 'virtualmachine': + this.imageType = 1; + break; + + case 'smartmachine': + this.imageType = 2; + break; + } + + this.createForm(); + } +} diff --git a/app/src/app/networking/firewall-editor/firewall-editor.component.html b/app/src/app/networking/firewall-editor/firewall-editor.component.html new file mode 100644 index 0000000..3624650 --- /dev/null +++ b/app/src/app/networking/firewall-editor/firewall-editor.component.html @@ -0,0 +1,109 @@ +
+
+ + +
+

Firewall editor

+ +
+
+
Action
+ + +
+ +
+
Protocol
+ + +
+
+
+ Port + Type and code +
+ + +
+ +
+
From
+
+
+
+
+
+
+ {{ control.value.type }} + {{ instances[control.value.config] || control.value.config }} +
+ + +
+
+
+ + +
+
+
+ +
+
To
+
+
+
+
+
+
+ {{ control.value.type }} + {{ instances[control.value.config] || control.value.config }} +
+ + +
+
+
+ + +
+
+
+
+ +
+
+ +
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/networking/firewall-editor/firewall-editor.component.scss b/app/src/app/networking/firewall-editor/firewall-editor.component.scss new file mode 100644 index 0000000..c16ab2c --- /dev/null +++ b/app/src/app/networking/firewall-editor/firewall-editor.component.scss @@ -0,0 +1,40 @@ +h5 +{ + color: #ff9c07; +} + +.select-list +{ + .form-check .form-check-label + { + padding: .25rem .5rem .25rem .25rem; + text-transform: uppercase; + font-family: "Bebas Neue", sans-serif; + + small + { + font-size: .7rem; + } + } + + .list-group-item + { + border: none; + background: transparent; + color: #8881ff; + padding: .5rem .5rem .5rem .75rem; + } + + .rule + { + color: #ff9c07; + text-transform: uppercase; + + b + { + margin-left: .5rem; + color: #8881ff; + text-transform: none; + } + } +} diff --git a/app/src/app/networking/firewall-editor/firewall-editor.component.spec.ts b/app/src/app/networking/firewall-editor/firewall-editor.component.spec.ts new file mode 100644 index 0000000..7647e3f --- /dev/null +++ b/app/src/app/networking/firewall-editor/firewall-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FirewallEditorComponent } from './firewall-editor.component'; + +describe('FirewallEditorComponent', () => { + let component: FirewallEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FirewallEditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FirewallEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/firewall-editor/firewall-editor.component.ts b/app/src/app/networking/firewall-editor/firewall-editor.component.ts new file mode 100644 index 0000000..e9fd346 --- /dev/null +++ b/app/src/app/networking/firewall-editor/firewall-editor.component.ts @@ -0,0 +1,211 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { ReplaySubject, Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { FirewallRule } from '../models/firewall-rule'; +import { FirewallRuleRequest } from '../models/firewall-rule'; +import { FirewallService } from '../helpers/firewall.service'; +import { ToastrService } from 'ngx-toastr'; +import { InstancesService } from '../../instances/helpers/instances.service'; + +@Component({ + selector: 'app-firewall-editor', + templateUrl: './firewall-editor.component.html', + styleUrls: ['./firewall-editor.component.scss'] +}) +export class FirewallEditorComponent implements OnInit, OnDestroy +{ + @Input() + firewallRule: FirewallRule; + + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + rule: string; + canAddFromRule: boolean; + canAddToRule: boolean; + protocolConfigRegex: string; + instances = {}; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly toastr: ToastrService, + private readonly instancesService: InstancesService, + private readonly firewallService: FirewallService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + + this.instancesService.get() + .subscribe(x => + { + this.instances = x.reduce((a, b) => + { + a[b.id] = b.name; + return a; + }, {}); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + const from = this.fb.array( + this.firewallRule?.fromArray + ? this.firewallRule.fromArray.map(x => this.fb.group({ type: x.type, config: x.config })) + : [], + { validators: [Validators.required] } + ); + + this.canAddFromRule = true; + + const to = this.fb.array( + this.firewallRule?.toArray + ? this.firewallRule.toArray.map(x => this.fb.group({ type: x.type, config: x.config })) + : [], + { validators: [Validators.required] } + ); + + this.canAddToRule = true; + + this.editorForm = this.fb.group( + { + action: [this.firewallRule?.action.toUpperCase(), [Validators.required]], + protocol: [this.firewallRule?.protocol.toLowerCase(), [Validators.required]], + protocolConfig: [this.firewallRule?.protocolConfig, [Validators.required]], + from, + to, + description: [this.firewallRule?.description || ''] + }); + + this.setProtocolConfigValidators(this.editorForm.get('protocol').value); + + this.editorForm.get('protocol').valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe(this.setProtocolConfigValidators.bind(this)); + } + + // ---------------------------------------------------------------------------------------------------------------- + private setProtocolConfigValidators(protocol: string) + { + if (protocol === 'icmp') + this.protocolConfigRegex = '^([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])?(:)?([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'; + else + { + // Make sure there are no illegal characters in the input box + if (this.editorForm.get('protocolConfig').value) + this.editorForm.get('protocolConfig').setValue(this.editorForm.get('protocolConfig').value.replace(/:/g, '')); + + this.protocolConfigRegex = '^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9])$'; + } + + this.editorForm.get('protocolConfig').setValidators([Validators.required, Validators.pattern(this.protocolConfigRegex)]); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + + const changes = this.editorForm.getRawValue(); + + const firewallRule = new FirewallRule(); + firewallRule.id = this.firewallRule?.id; + firewallRule.action = changes.action; + firewallRule.protocol = changes.protocol; + firewallRule.protocolConfig = changes.protocolConfig; + firewallRule.fromArray = changes.from; + firewallRule.toArray = changes.to; + + const request = new FirewallRuleRequest(); + request.description = changes.description; + request.enabled = this.firewallRule ? this.firewallRule.enabled : true; + request.rule = this.firewallService.stringifyFirewallRule(firewallRule); + + const observable = this.firewallRule + ? this.firewallService.editFirewallRule(this.firewallRule.id, request) + : this.firewallService.addFirewallRule(request); + + observable.pipe(takeUntil(this.destroy$)) + .subscribe(response => + { + this.working = false; + + this.save.next(response); + + this.close(); + }, err => + { + const message = err.error.errors ? err.error.errors[0].message : err.error.message; + this.toastr.error(`Failed to save firewall rule (${message})`); + + this.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + addFromRule(rule: { type: string; config: string }) + { + const array = this.editorForm.get('from') as FormArray; + + if (['any', 'all'].includes(rule.type) || array.controls.find(x => ['any', 'all'].includes(x.get('type').value))) + array.clear(); + + array.push(this.fb.group({ type: rule.type, config: rule.config })); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeFromRule(index: number) + { + const array = this.editorForm.get('from') as FormArray; + + array.removeAt(index); + } + + // ---------------------------------------------------------------------------------------------------------------- + addToRule(rule: { type: string; config: string }) + { + const array = this.editorForm.get('to') as FormArray; + + if (['any', 'all'].includes(rule.type) || array.controls.find(x => ['any', 'all'].includes(x.get('type').value))) + array.clear(); + + array.push(this.fb.group({ type: rule.type, config: rule.config })); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeToRule(index: number) + { + const array = this.editorForm.get('to') as FormArray; + + array.removeAt(index); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.html b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.html new file mode 100644 index 0000000..98682ed --- /dev/null +++ b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.html @@ -0,0 +1,44 @@ + + +
+
+
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ + +
+
+
diff --git a/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.scss b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.spec.ts b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.spec.ts new file mode 100644 index 0000000..9ad0fe5 --- /dev/null +++ b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FirewallRuleEditorComponent } from './firewall-rule-editor.component'; + +describe('FirewallRuleEditorComponent', () => { + let component: FirewallRuleEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FirewallRuleEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FirewallRuleEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.ts b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.ts new file mode 100644 index 0000000..80ae935 --- /dev/null +++ b/app/src/app/networking/firewall-rule-editor/firewall-rule-editor.component.ts @@ -0,0 +1,194 @@ +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { Instance } from '../../instances/models/instance'; +import { InstancesService } from '../../instances/helpers/instances.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-firewall-rule-editor', + templateUrl: './firewall-rule-editor.component.html', + styleUrls: ['./firewall-rule-editor.component.scss'] +}) +export class FirewallRuleEditorComponent implements OnInit, OnDestroy +{ + @Input() + disabled: boolean; + + @Output() + saved = new EventEmitter(); + + instances: Instance[]; + editorVisible: boolean; + editorForm: FormGroup; + keyRegex = '^[A-Za-z0-9-_]+$'; + keyPlaceholder: string; + + private destroy$ = new Subject(); + + // -------------------------------------------------------------------------------------------------- + constructor(private readonly elementRef: ElementRef, + private readonly fb: FormBuilder, + private readonly instancesService: InstancesService) + { + this.instancesService.get().subscribe(x => this.instances = x); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + type: [null, Validators.required], + ruleSettings: this.fb.group( + { + key: [], + value: [], + config: [] + }) + }); + + // Dynamically configure validators + this.editorForm.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => + { + this.editorForm.get(['ruleSettings', 'key']).setValue(null); + this.editorForm.get(['ruleSettings', 'value']).setValue(null); + + this.editorForm.get('ruleSettings').clearValidators(); + + setTimeout(() => + { + if (type === 'subnet') + { + this.keyPlaceholder = 'Eg: 192.168.0.1/32'; + this.keyRegex = '^[0-9./]+$'; + + this.editorForm.get(['ruleSettings', 'key']).setValidators([ + Validators.required, + Validators.pattern('^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$') + ]); + } + else if (type === 'ip') + { + this.keyPlaceholder = 'Eg: 192.168.0.1'; + this.keyRegex = '^[0-9.]+$'; + + this.editorForm.get(['ruleSettings', 'key']).setValidators([ + Validators.required, + Validators.pattern('^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') + ]); + } + else if (type === 'vm') + { + this.keyRegex = '^.+$'; + + this.editorForm.get(['ruleSettings', 'value']).setValidators([Validators.required]); + } + else if (type === 'tag') + { + this.keyPlaceholder = 'Key'; + this.keyRegex = '^[A-Za-z0-9-_]+$'; + + this.editorForm.get(['ruleSettings', 'key']).setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9-_]+$')]); + this.editorForm.get(['ruleSettings', 'value']).setValidators([Validators.required]); + } + else + { + this.keyRegex = '^.+$'; + } + }, 0); + }); + } + + // -------------------------------------------------------------------------------------------------- + showEditor() + { + if (this.disabled) return; + + this.editorVisible = true; + + addEventListener('click', this.onDocumentClick.bind(this)); + } + + // -------------------------------------------------------------------------------------------------- + saveChanges() + { + event.preventDefault(); + event.stopPropagation(); + + this.editorVisible = false; + + this.removeEventListeners(); + + let config: string; + + if (this.editorForm.get('type').value === 'all') + config = 'vms'; + else if (this.editorForm.get('type').value === 'tag') + config = `"${this.editorForm.get(['ruleSettings', 'key']).value}" = "${this.editorForm.get(['ruleSettings', 'value']).value}"`; + else if (this.editorForm.get('type').value === 'vm') + config = this.editorForm.get(['ruleSettings', 'key']).value.toLowerCase(); + else + config = this.editorForm.get(['ruleSettings', 'key']).value; + + this.saved.emit( + { + type: this.editorForm.get('type').value, + config: config || '' + }); + + this.editorForm.reset(); + } + + // -------------------------------------------------------------------------------------------------- + cancelChanges(event: MouseEvent) + { + this.editorVisible = false; + + this.removeEventListeners(); + + this.editorForm.reset(); + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('document:keydown', ['$event']) + returnPressed(event) + { + if (event.currentTarget === this.elementRef.nativeElement && event.keyCode === 13) + { + event.preventDefault(); + event.stopPropagation(); + + this.saveChanges(); + } + } + + // -------------------------------------------------------------------------------------------------- + protected onDocumentClick(event: MouseEvent) + { + if (!this.elementRef.nativeElement.contains(event.target)) + this.cancelChanges(event); + } + + // -------------------------------------------------------------------------------------------------- + private removeEventListeners() + { + removeEventListener('click', this.onDocumentClick); + removeEventListener('document:keydown', this.returnPressed); + } + + // -------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } + + // -------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + this.removeEventListeners(); + } +} diff --git a/app/src/app/networking/firewall-rules/firewall-rules.component.html b/app/src/app/networking/firewall-rules/firewall-rules.component.html new file mode 100644 index 0000000..52394a6 --- /dev/null +++ b/app/src/app/networking/firewall-rules/firewall-rules.component.html @@ -0,0 +1,132 @@ +
+
+
+
+ +
+ + + + +
+ + +
+ +
+ + +
+
+
+ +
+ Loading... +
+
+ +
+
+
+

+ There are no firewall rules yet. +

+ + + + + + + + + + + + + + + + + + +
ActionStatusDescription
+ + {{ fw.action }} + + + + {{ fw.protocol }} + {{ fw.protocolConfig }} + + + From + + {{ from.type }} + {{ instances[from.config] || from.config }} + + + + To + + {{ to.type }} + {{ instances[to.config] || to.config }} + + + +
+ +
+
+
{{ fw.description }}
+
+
+ + + + +
+
+
+
+
+
diff --git a/app/src/app/networking/firewall-rules/firewall-rules.component.scss b/app/src/app/networking/firewall-rules/firewall-rules.component.scss new file mode 100644 index 0000000..6fb03b3 --- /dev/null +++ b/app/src/app/networking/firewall-rules/firewall-rules.component.scss @@ -0,0 +1,45 @@ +.table-responsive +{ + background-color: rgba(16, 21, 39, 0.75); + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + transition: box-shadow 0.15s ease-out; + border-radius: .25rem; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgb(18 203 240 / 40%), 0 0 10px 3px #0e162a; + } + + .rule + { + text-transform: uppercase; + color: #3d5e8e; + } + + .highlight + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: normal; + } + + .text-truncate + { + max-width: 350px; + } + + .inline-list-item + .inline-list-item + { + padding-left: .25rem; + + &:before + { + content: attr(text); + color: #3d5e8e; + } + } +} diff --git a/app/src/app/networking/firewall-rules/firewall-rules.component.spec.ts b/app/src/app/networking/firewall-rules/firewall-rules.component.spec.ts new file mode 100644 index 0000000..d5b7282 --- /dev/null +++ b/app/src/app/networking/firewall-rules/firewall-rules.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FirewallRulesComponent } from './firewall-rules.component'; + +describe('FirewallRulesComponent', () => { + let component: FirewallRulesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FirewallRulesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FirewallRulesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/firewall-rules/firewall-rules.component.ts b/app/src/app/networking/firewall-rules/firewall-rules.component.ts new file mode 100644 index 0000000..b83f4ea --- /dev/null +++ b/app/src/app/networking/firewall-rules/firewall-rules.component.ts @@ -0,0 +1,242 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FirewallEditorComponent } from '../firewall-editor/firewall-editor.component'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { distinctUntilChanged, first, takeUntil, debounceTime, filter, switchMap } from 'rxjs/operators'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import Fuse from 'fuse.js'; +import { Subject, ReplaySubject } from 'rxjs'; +import { ToastrService } from 'ngx-toastr'; +import { FirewallRule } from '../models/firewall-rule'; +import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component'; +import { InstancesService } from '../../instances/helpers/instances.service'; +import { FirewallService } from '../helpers/firewall.service'; +import { sortArray } from '../../helpers/utils.service'; + +@Component({ + selector: 'app-firewall-rules', + templateUrl: './firewall-rules.component.html', + styleUrls: ['./firewall-rules.component.scss'] +}) +export class FirewallRulesComponent implements OnInit, OnDestroy +{ + firewallRules: FirewallRule[]; + listItems: FirewallRule[]; + loadingIndicator = true; + editorForm: FormGroup; + instances = {}; + + private readonly fuseJsOptions: {}; + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly firewallService: FirewallService, + private readonly instancesService: InstancesService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService, + private readonly fb: FormBuilder) + { + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: true, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'description', weight: .9 } + ] + }; + + this.instancesService.get() + .subscribe(x => + { + this.instances = x.reduce((a, b) => + { + a[b.id] = b.name; + return a; + }, {}); + }); + + this.createForm(); + + this.getFirewallRules(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + searchTerm: [''], + sortProperty: ['action'] + }); + + this.editorForm.get('searchTerm').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('sortProperty').valueChanges + .pipe( + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + } + + // ---------------------------------------------------------------------------------------------------------------- + private applyFiltersAndSort() + { + let listItems: FirewallRule[] = null; + + const searchTerm = this.editorForm.get('searchTerm').value; + if (searchTerm.length >= 2) + { + const fuse = new Fuse(this.firewallRules, this.fuseJsOptions); + const fuseResults = fuse.search(searchTerm); + listItems = fuseResults.map(x => x.item as FirewallRule); + } + + if (!listItems) + listItems = [...this.firewallRules]; + + this.listItems = sortArray(listItems, this.editorForm.get('sortProperty').value); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getFirewallRules() + { + this.firewallService.getFirewallRules() + .subscribe(firewallRules => + { + this.firewallRules = firewallRules.map(this.firewallService.parseFirewallRule.bind(this)); + + this.applyFiltersAndSort(); + + this.loadingIndicator = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + setSortProperty(propertyName: string) + { + this.editorForm.get('sortProperty').setValue(propertyName); + } + + // ---------------------------------------------------------------------------------------------------------------- + clearSearch() + { + this.editorForm.get('searchTerm').setValue(''); + } + + // ---------------------------------------------------------------------------------------------------------------- + showEditor(firewallRule?: FirewallRule) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { firewallRule } + }; + + const modalRef = this.modalService.show(FirewallEditorComponent, modalConfig); + modalRef.setClass('modal-lg'); + + modalRef.content.save.pipe(first()).subscribe(x => + { + const fw = this.firewallService.parseFirewallRule(x); + + if (firewallRule) + { + const index = this.firewallRules.findIndex(f => f.id === fw.id); + if (index >= 0) + this.firewallRules.splice(index, 1, fw); + + this.toastr.info(`The firewall rule "${fw.rule}" has been updated`); + } + else + { + this.firewallRules.push(fw); + + this.toastr.info(`The firewall rule "${fw.rule}" has been created`); + } + + this.applyFiltersAndSort(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteFirewallRule(firewallRule: FirewallRule) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete this firewall rule?`, + confirmButtonText: 'Yes, delete it', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe( + first(), + switchMap(() => this.firewallService.deleteFirewallRule(firewallRule)) + ) + .subscribe(() => + { + const index = this.firewallRules.findIndex(x => x.id === firewallRule.id); + if (index >= 0) + this.firewallRules.splice(index, 1); + + this.applyFiltersAndSort(); + + this.toastr.info(`The firewall rule has been deleted`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to remove the firewall rule ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + toggleFirewallRule(event, firewallRule: FirewallRule) + { + const enable: boolean = event.target.checked; + + firewallRule.working = true; + + this.firewallService.toggleFirewallRule(firewallRule, enable) + .subscribe(x => + { + this.toastr.info(`The firewall rule "${firewallRule.description || firewallRule.rule}" has been ${enable ? 'enabled' : 'disabled'}`); + firewallRule.working = false; + }, err => + { + // Revert back to the previous value + firewallRule.enabled = !enable; + + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to toggle firewall rule "${firewallRule.description || firewallRule.rule}" ${errorDetails})`); + firewallRule.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/networking/helpers/firewall.service.spec.ts b/app/src/app/networking/helpers/firewall.service.spec.ts new file mode 100644 index 0000000..21f6fac --- /dev/null +++ b/app/src/app/networking/helpers/firewall.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { FirewallService } from './firewall.service'; + +describe('FirewallService', () => { + let service: FirewallService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(FirewallService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/helpers/firewall.service.ts b/app/src/app/networking/helpers/firewall.service.ts new file mode 100644 index 0000000..3a8e889 --- /dev/null +++ b/app/src/app/networking/helpers/firewall.service.ts @@ -0,0 +1,146 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { delay, filter, first, flatMap, map, mergeMapTo, repeatWhen, switchMap, switchMapTo, take, takeUntil, tap } from 'rxjs/operators'; +import { concat, empty, of, range, throwError, zip } from 'rxjs'; +import { Cacheable } from 'ts-cacheable'; +import { FirewallRuleResponse } from '../models/firewall-rule'; +import { FirewallRule } from '../models/firewall-rule'; +import { FirewallRuleRequest } from '../models/firewall-rule'; + +const cacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class FirewallService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getFirewallRules(): Observable + { + return this.httpClient.get(`/api/my/fwrules`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: cacheBuster$ + }) + getInstanceFirewallRules(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/fwrules`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addFirewallRule(firewallRule: FirewallRuleRequest): Observable + { + return this.httpClient.post(`/api/my/fwrules`, firewallRule) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + editFirewallRule(firewallRuleId: string, firewallRule: FirewallRuleRequest): Observable + { + return this.httpClient.post(`/api/my/fwrules/${firewallRuleId}`, firewallRule) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + toggleFirewallRule(firewallRule: FirewallRuleResponse, enable: boolean): Observable + { + return this.httpClient.post(`/api/my/fwrules/${firewallRule.id}/${enable ? 'enable' : 'disable'}`, {}) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteFirewallRule(firewallRule: FirewallRuleResponse): Observable + { + return this.httpClient.delete(`/api/my/fwrules/${firewallRule.id}`) + .pipe(tap(() => cacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + parseFirewallRule(firewallRule: FirewallRuleResponse): FirewallRule + { + let ruleAction = ' ALLOW '; + let indexOfAction = firewallRule.rule.indexOf(ruleAction); + if (indexOfAction < 0) + { + ruleAction = ' BLOCK '; + indexOfAction = firewallRule.rule.indexOf(ruleAction); + } + + const indexOfTo = firewallRule.rule.indexOf(' TO '); + + const from = firewallRule.rule.substring('FROM '.length, indexOfTo).replace('(', '').replace(')', '').split(' OR '); + const fromArray = from.map(x => + { + const parts = x.split(' '); + + return { + type: parts[0], + config: x.substr(parts[0].length + 1) + } + }); + + const to = firewallRule.rule.substring(indexOfTo + ' TO '.length, indexOfAction).replace('(', '').replace(')', '').split(' OR '); + const toArray = to.map(x => + { + const parts = x.split(' '); + + return { + type: parts[0], + config: x.substr(parts[0].length + 1) + } + }); + + const protocolAndPortOrCode = firewallRule.rule.substr(indexOfAction + ruleAction.length).split(' '); + + const rule = new FirewallRule(); + Object.assign(rule, firewallRule); + + rule.fromArray = fromArray; + rule.fromValue = from.join(','); + rule.toArray = toArray; + rule.toValue = to.join(','); + rule.action = ruleAction.trim(); + rule.protocol = protocolAndPortOrCode[0]; + rule.protocolConfig = protocolAndPortOrCode[2] + (protocolAndPortOrCode.length > 3 ? `:${protocolAndPortOrCode[4]}` : ''); + rule.rule = `${rule.action} ${rule.protocol} ${rule.protocolConfig} FROM ${rule.fromValue} TO ${rule.toValue}`.toUpperCase(); + + return rule; + } + + // ---------------------------------------------------------------------------------------------------------------- + stringifyFirewallRule(firewallRule: FirewallRule): string + { + const protocolConfigParts = firewallRule.protocolConfig.split(':'); + const protocolConfig = firewallRule.protocol.toUpperCase() === 'ICMP' + ? `TYPE ${protocolConfigParts[0]} CODE ${protocolConfigParts[1]}` + : `PORT ${protocolConfigParts[0]}`; + + let from = '', to = ''; + + if (firewallRule.fromArray) + { + const fromArray = firewallRule.fromArray.map(x => `${x.type} ${x.config}`); + from = `${fromArray.length > 1 ? `(${fromArray.join(' OR ')})` : fromArray[0]}`; + } + + if (firewallRule.toArray) + { + const toArray = firewallRule.toArray.map(x => `${x.type} ${x.config}`); + to = `${toArray.length > 1 ? `(${toArray.join(' OR ')})` : toArray[0]}`; + } + + if (!from || !to) + return ''; + + return `FROM ${from} TO ${to} ${firewallRule.action.toUpperCase()} ${firewallRule.protocol.toUpperCase()} ${protocolConfig}`; + } +} diff --git a/app/src/app/networking/helpers/networking.service.spec.ts b/app/src/app/networking/helpers/networking.service.spec.ts new file mode 100644 index 0000000..f5d1b7b --- /dev/null +++ b/app/src/app/networking/helpers/networking.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { NetworkingService } from './networking.service'; + +describe('NetworkingService', () => { + let service: NetworkingService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(NetworkingService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/helpers/networking.service.ts b/app/src/app/networking/helpers/networking.service.ts new file mode 100644 index 0000000..b304456 --- /dev/null +++ b/app/src/app/networking/helpers/networking.service.ts @@ -0,0 +1,185 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { delay, filter, first, flatMap, map, mergeMapTo, repeatWhen, switchMap, switchMapTo, take, takeUntil, tap } from 'rxjs/operators'; +import { concat, empty, of, range, throwError, zip } from 'rxjs'; +import { Cacheable } from 'ts-cacheable'; +import { Network } from '../models/network'; +import { Nic } from '../../instances/models/nic'; +import { VirtualAreaNetwork } from '../models/vlan'; +import { VirtualAreaNetworkRequest } from '../models/vlan'; +import { EditNetworkRequest } from '../models/network'; +import { AddNetworkRequest } from '../models/network'; + +const networksCacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class NetworkingService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + getDataCenters(): Observable + { + return this.httpClient.get(`/api/my/datacenters`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: networksCacheBuster$ + }) + getNetworks(): Observable + { + return this.httpClient.get(`/api/my/networks`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: networksCacheBuster$ + }) + getNetwork(id: string): Observable + { + return this.httpClient.get(`/api/my/networks/${id}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getNetworkIPs(networkId: string): Observable + { + return this.httpClient.get(`/api/my/networks/${networkId}/ips`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getNetworkIP(networkId: string, ipAddress: string): Observable + { + return this.httpClient.get(`/api/my/networks/${networkId}/ips/${ipAddress}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + editNetworkIP(networkId: string, ipAddress: string, value: {}): Observable + { + return this.httpClient.put(`/api/my/networks/${networkId}/ips/${ipAddress}`, value); + } + + // ---------------------------------------------------------------------------------------------------------------- + getFabricVirtualLocalAreaNetworks(): Observable + { + return this.httpClient.get(`/api/my/fabrics/default/vlans`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getFabricVirtualLocalAreaNetwork(vlanId: number): Observable + { + return this.httpClient.get(`/api/my/fabrics/default/vlans/${vlanId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + // "id" myt be between 0-4095 + addFabricVirtualLocalAreaNetwork(vlan: VirtualAreaNetworkRequest): Observable + { + return this.httpClient.post(`/api/my/fabrics/default/vlans`, vlan); + } + + // ---------------------------------------------------------------------------------------------------------------- + editFabricVirtualLocalAreaNetwork(id: number, name: string, description?: string): Observable + { + return this.httpClient.put(`/api/my/fabrics/default/vlans/${id}`, + { + name, + description + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + // NOTE: There must be no networks on that VLAN in order for the VLAN to be deleted. + deleteFabricVirtualLocalAreaNetwork(vlanId: number): Observable + { + return this.httpClient.delete(`/api/my/fabrics/default/vlans/${vlanId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getFabricNetworks(vlanId: number): Observable + { + return this.httpClient.get(`/api/my/fabrics/default/vlans/${vlanId}/networks`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getFabricNetwork(vlanId: number, networkId: number): Observable + { + return this.httpClient.get(`/api/my/fabrics/default/vlans/${vlanId}/networks/${networkId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addFabricNetwork(vlanId: number, network: AddNetworkRequest): Observable + { + return this.httpClient.post(`/api/my/fabrics/default/vlans/${vlanId}/networks`, network) + .pipe(tap(() => networksCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + editFabricNetwork(vlanId: number, networkId: string, network: EditNetworkRequest): Observable + { + return this.httpClient.put(`/api/my/fabrics/default/vlans/${vlanId}/networks/${networkId}`, network) + .pipe(tap(() => networksCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteFabricNetworks(vlanId: number, networkId: string): Observable + { + return this.httpClient.delete(`/api/my/fabrics/default/vlans/${vlanId}/networks/${networkId}`) + .pipe(tap(() => networksCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + getNics(instanceId: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/nics`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getNic(instanceId: string, macAddress: string): Observable + { + return this.httpClient.get(`/api/my/machines/${instanceId}/nics/${macAddress.replace(/:/g, '')}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getNicUntilExpectedState(instance: any, nic: Nic, expectedStates: string[], callbackFn?: NicCallbackFunction, maxRetries = 30): Observable + { + // Keep polling the snapshot until it reaches the expected state + return this.getNic(instance.id, nic.mac) + .pipe( + tap(x => callbackFn && callbackFn(x)), + repeatWhen(x => + { + let retries = 0; + + return x.pipe( + delay(3000), + map(() => + { + if (retries++ === maxRetries) + throw { error: { message: `Failed to retrieve the current status for network interface "${nic.mac}"` } }; + }) + ); + }), + filter(x => expectedStates.includes(x.state)), + take(1) // needed to stop the repeatWhen loop + ); + } + + // ---------------------------------------------------------------------------------------------------------------- + addNic(instanceId: string, networkId: string): Observable + { + return this.httpClient.post(`/api/my/machines/${instanceId}/nics`, { network: networkId }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteNic(instanceId: string, macAddress: string): Observable + { + return this.httpClient.delete(`/api/my/machines/${instanceId}/nics/${macAddress.replace(/:/g, '')}`); + } +} + +export type NicCallbackFunction = ((nic: Nic) => void); diff --git a/app/src/app/networking/models/firewall-rule.ts b/app/src/app/networking/models/firewall-rule.ts new file mode 100644 index 0000000..0dcadd5 --- /dev/null +++ b/app/src/app/networking/models/firewall-rule.ts @@ -0,0 +1,24 @@ +export class FirewallRuleRequest +{ + description: string; + enabled: boolean; + rule: string; +} + +export class FirewallRuleResponse extends FirewallRuleRequest +{ + id: string; + global: boolean; +} + +export class FirewallRule extends FirewallRuleResponse +{ + action: string; + fromArray: { type: string; config: string }[]; + fromValue: string; + toArray: { type: string; config: string }[]; + toValue: string; + protocol: string; + protocolConfig: string; + working?: boolean; +} diff --git a/app/src/app/networking/models/network.ts b/app/src/app/networking/models/network.ts new file mode 100644 index 0000000..afd22be --- /dev/null +++ b/app/src/app/networking/models/network.ts @@ -0,0 +1,23 @@ +export class EditNetworkRequest +{ + name: string; + description: string; + provision_start_ip: string; + provision_end_ip: string; + resolvers: string[]; + routes: {}; // Eg: { "10.25.1.0/21": "10.50.1.2", "10.27.1.0/21": "10.50.1.3" } +} + +export class AddNetworkRequest extends EditNetworkRequest +{ + subnet: string; + gateway: string; + internet_nat: boolean; +} + +export class Network extends AddNetworkRequest +{ + id: string; + public: boolean; + fabric: boolean; +} diff --git a/app/src/app/networking/models/vlan.ts b/app/src/app/networking/models/vlan.ts new file mode 100644 index 0000000..fe0f011 --- /dev/null +++ b/app/src/app/networking/models/vlan.ts @@ -0,0 +1,16 @@ +import { Network } from './network'; + +export class VirtualAreaNetworkRequest +{ + vlan_id: number; + name: string; + description: string; +} + +export class VirtualAreaNetwork extends VirtualAreaNetworkRequest +{ + networks: Network[]; + public: boolean; + working: boolean; + expanded: boolean; +} diff --git a/app/src/app/networking/network-editor/network-editor.component.html b/app/src/app/networking/network-editor/network-editor.component.html new file mode 100644 index 0000000..ae79a03 --- /dev/null +++ b/app/src/app/networking/network-editor/network-editor.component.html @@ -0,0 +1,118 @@ +
+
+ + +
+

Network editor

+ +
All fields except "DNS resolvers" and "Routes" are mandatory
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ CIDR format: #.#.#.#/## +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
DNS resolvers
+
+
+
+
+
+ {{ control.value.resolver }} + + +
+
+
+ + +
+
+
+
+
Routes
+
+
+
+
+
+ {{ control.value.key }} + + {{ control.value.value }} + + +
+
+
+ + +
+
+
+
+ + + + +
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/networking/network-editor/network-editor.component.scss b/app/src/app/networking/network-editor/network-editor.component.scss new file mode 100644 index 0000000..bc204c1 --- /dev/null +++ b/app/src/app/networking/network-editor/network-editor.component.scss @@ -0,0 +1,97 @@ +h5 +{ + color: #ff9c07; +} + +.form-check +{ + display: flex; + align-items: center; + padding: 0; + margin: 0 0 0 1.5rem; + cursor: pointer; + flex-grow: 1; + + .form-check-input + { + margin-right: .5rem; + float: none; + width: 1.4em; + max-width: 1rem; + margin-bottom: .25rem; + cursor: inherit; + background-color: #0dc3e9; + border-color: #0dc3e9; + box-shadow: 0 0 0 1px rgb(12, 19, 33, .5) inset; + + &:checked + { + background-color: #ff9c07; + border-color: #ff9c07; + + &:not(:focus) + { + box-shadow: none; + } + } + + &:checked[type=radio] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%230c1321'/%3e%3c/svg%3e"); + } + + &:checked[type=checkbox] + { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230c1321' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + } + + &:focus + { + box-shadow: 0 0 0 0.25rem rgba(13, 195, 233, .5); + } + + &:checked:focus + { + box-shadow: 0 0 0 0.25rem rgba(255, 156, 7, .25); + } + } + + .form-check-label + { + cursor: inherit; + width: 100%; + padding: .75rem 0; + font-family: "Bebas Neue", sans-serif; + font-size: 1.2rem; + } +} + +.form-check-input:checked + .form-check-label, +.form-check-input:checked + .form-check-label .package-specs, +.form-check-input:checked + .form-check-label .h3 +{ + color: #ff9c07; +} + +.select-list +{ + .form-check .form-check-label + { + padding: .25rem .5rem .25rem .25rem; + text-transform: uppercase; + font-family: "Bebas Neue", sans-serif; + + small + { + font-size: .7rem; + } + } + + .list-group-item + { + border: none; + background: transparent; + color: #8881ff; + padding: .5rem .5rem .5rem .75rem; + } +} diff --git a/app/src/app/networking/network-editor/network-editor.component.spec.ts b/app/src/app/networking/network-editor/network-editor.component.spec.ts new file mode 100644 index 0000000..97bae26 --- /dev/null +++ b/app/src/app/networking/network-editor/network-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NetworkEditorComponent } from './network-editor.component'; + +describe('NetworkEditorComponent', () => { + let component: NetworkEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NetworkEditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NetworkEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/network-editor/network-editor.component.ts b/app/src/app/networking/network-editor/network-editor.component.ts new file mode 100644 index 0000000..e618b0d --- /dev/null +++ b/app/src/app/networking/network-editor/network-editor.component.ts @@ -0,0 +1,236 @@ +import{ Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { NetworkingService } from '../helpers/networking.service'; +import { ToastrService } from 'ngx-toastr'; +import { VirtualAreaNetwork } from '../models/vlan'; +import { AddNetworkRequest, Network } from '../models/network'; +import { EditNetworkRequest } from '../models/network'; + +@Component({ + selector: 'app-network-editor', + templateUrl: './network-editor.component.html', + styleUrls: ['./network-editor.component.scss'] +}) +export class NetworkEditorComponent implements OnInit, OnDestroy +{ + @Input() + vlan: VirtualAreaNetwork; + + @Input() + network: Network; + + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + canAddResolver: boolean; + canAddRoute: boolean; + // IPv4 address with optional /nn on the end with values from 0 - 32 + ipOrSubnetRegex = '^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$'; + // IPv4 address with mandatory /nn on the end with values from 0 - 32 + subnetRegex = '^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$'; + ipRegex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly networkingService: NetworkingService, + private readonly toastr: ToastrService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + const resolvers = this.fb.array(this.network?.resolvers + ? this.network.resolvers.map(ip => + { + return this.fb.group( + { + resolver: [ip, Validators.pattern(this.ipRegex)] + }); + }) + : [] + ); + + this.canAddResolver = resolvers.length < 4; + + const routes = this.fb.array(this.network?.routes + ? Object.keys(this.network.routes).map(key => + { + return this.fb.group( + { + key: [key, [Validators.required, Validators.pattern(this.ipOrSubnetRegex)]], + value: [this.network.routes[key], [Validators.required, Validators.pattern(this.ipRegex)]] + }); + }) + : [] + ); + + this.canAddRoute = routes.length < 4; + + this.editorForm = this.fb.group( + { + name: [this.network?.name, Validators.required], + subnet: [{ value: this.network?.subnet, disabled: !!this.network }, [Validators.required, Validators.pattern(this.subnetRegex)]], + startIp: [this.network?.provision_start_ip, [Validators.required, Validators.pattern(this.ipRegex)]], + endIp: [this.network?.provision_end_ip, [Validators.required, Validators.pattern(this.ipRegex)]], + gateway: [{ value: this.network?.gateway, disabled: !!this.network }, [Validators.required, Validators.pattern(this.ipRegex)]], + resolvers, + routes, + description: [this.network?.description, [Validators.maxLength(64)]], + nat: [{ value: this.network?.internet_nat, disabled: this.network }] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + addResolver(resolver: string) + { + const array = this.editorForm.get('resolvers') as FormArray; + + const control = this.fb.group( + { + resolver: [resolver, Validators.pattern(this.ipRegex)] + }); + + array.push(control); + + this.canAddResolver = array.length < 4; + } + + // ---------------------------------------------------------------------------------------------------------------- + removeResolver(index: number) + { + const array = this.editorForm.get('resolvers') as FormArray; + + array.removeAt(index); + + this.canAddResolver = array.length < 4; + } + + // ---------------------------------------------------------------------------------------------------------------- + addRoute(route: { key: string; value: string }) + { + const array = this.editorForm.get('routes') as FormArray; + + const control = this.fb.group( + { + key: [route.key, [Validators.required, Validators.pattern(this.ipOrSubnetRegex)]], + value: [route.value, [Validators.required, Validators.pattern(this.ipRegex)]] + }); + + array.push(control); + + this.canAddRoute = array.length < 4; + } + + // ---------------------------------------------------------------------------------------------------------------- + removeRoute(index: number) + { + const array = this.editorForm.get('routes') as FormArray; + + array.removeAt(index); + + this.canAddRoute = array.length < 4; + } + + // ---------------------------------------------------------------------------------------------------------------- + private addNetwork(changes: any) + { + const network = new AddNetworkRequest(); + network.name = changes.name; + network.description = changes.description || ''; + network.subnet = changes.subnet; + network.provision_start_ip = changes.startIp; + network.provision_end_ip = changes.endIp; + network.gateway = changes.gateway; + network.internet_nat = changes.nat; + network.resolvers = changes.resolvers?.map(x => x.resolver); + network.routes = changes.routes?.reduce((routes, route) => + { + routes[route.key] = route.value; + return routes; + }, {}); + + return this.networkingService.addFabricNetwork(this.vlan.vlan_id, network); + } + + // ---------------------------------------------------------------------------------------------------------------- + private editNetwork(changes: any) + { + const network = new EditNetworkRequest(); + network.name = changes.name; + network.description = changes.description || ''; + network.provision_start_ip = changes.startIp; + network.provision_end_ip = changes.endIp; + network.resolvers = changes.resolvers?.map(x => x.resolver); + network.routes = changes.routes?.reduce((routes, route) => + { + routes[route.key] = route.value; + return routes; + }, {}); + + return this.networkingService.editFabricNetwork(this.vlan.vlan_id, this.network.id, network); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + + const changes = this.editorForm.getRawValue(); + + const observable = this.network ? this.editNetwork(changes) : this.addNetwork(changes); + + observable.subscribe(x => + { + const message = this.network + ? `The "${changes.name}" network has been succesfully updated` + : `The "${changes.name}" network has been succesfully created`; + this.toastr.info(message); + + this.working = false; + + this.save.next(x); + this.modalRef.hide(); + }, err => + { + this.toastr.error(err.error.message); + this.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + if (!this.vlan) + throw 'You must specify the VLAN'; + + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/networking/networking.component.html b/app/src/app/networking/networking.component.html new file mode 100644 index 0000000..9abe48c --- /dev/null +++ b/app/src/app/networking/networking.component.html @@ -0,0 +1 @@ +

networking works!

diff --git a/app/src/app/networking/networking.component.scss b/app/src/app/networking/networking.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/networking/networking.component.spec.ts b/app/src/app/networking/networking.component.spec.ts new file mode 100644 index 0000000..409aa03 --- /dev/null +++ b/app/src/app/networking/networking.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NetworkingComponent } from './networking.component'; + +describe('NetworkingComponent', () => { + let component: NetworkingComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NetworkingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NetworkingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/networking.component.ts b/app/src/app/networking/networking.component.ts new file mode 100644 index 0000000..7d82b5d --- /dev/null +++ b/app/src/app/networking/networking.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-networking', + templateUrl: './networking.component.html', + styleUrls: ['./networking.component.scss'] +}) +export class NetworkingComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/networking/networking.module.ts b/app/src/app/networking/networking.module.ts new file mode 100644 index 0000000..a05f22e --- /dev/null +++ b/app/src/app/networking/networking.module.ts @@ -0,0 +1,85 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { TranslateLoader } from '@ngx-translate/core'; +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { TranslateCompiler } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { NetworkingComponent } from './networking.component'; +import { NetworksComponent } from './networks/networks.component'; +import { NetworkEditorComponent } from './network-editor/network-editor.component'; +import { VirtualNetworkEditorComponent } from './virtual-network-editor/virtual-network-editor.component'; +import { FirewallEditorComponent } from './firewall-editor/firewall-editor.component'; +import { FirewallRulesComponent } from './firewall-rules/firewall-rules.component'; +import { FirewallRuleEditorComponent } from './firewall-rule-editor/firewall-rule-editor.component'; + +@NgModule({ + declarations: [ + NetworkingComponent, + NetworksComponent, + NetworkEditorComponent, + VirtualNetworkEditorComponent, + FirewallEditorComponent, + FirewallRulesComponent, + FirewallRuleEditorComponent, + ], + imports: [ + SharedModule, + RouterModule.forChild([ + { + path: '', + redirectTo: 'networks' + }, + { + path: 'networks', + component: NetworksComponent, + data: + { + title: 'networks.title', + subTitle: 'networks.subTitle', + icon: 'network-wired' + } + }, + { + path: 'firewall-rules', + component: FirewallRulesComponent, + data: + { + title: 'firewall.title', + subTitle: 'firewall.subTitle', + icon: 'fire-alt' + } + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('networking') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ], + entryComponents: [ + NetworkEditorComponent, + VirtualNetworkEditorComponent, + FirewallEditorComponent + ] +}) +export class NetworkingModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/app/networking/networks/networks.component.html b/app/src/app/networking/networks/networks.component.html new file mode 100644 index 0000000..25ffbb6 --- /dev/null +++ b/app/src/app/networking/networks/networks.component.html @@ -0,0 +1,152 @@ +
+
+
+
+ +
+ + + + +
+ + +
+ +
+ + +
+
+
+ +
+ Loading... +
+
+ +
+
+ + +
+

+ {{ vlan.name }} + + + {{ vlan.vlan_id }} + +

+ + +
+ +
+
+
+ Loading... +
+
+ +

+ The "{{ vlan.name }}" virtual network is empty. +

+ + + + + + + + + + + + + + + + + + + + + + +
NameSubnetIP RangeGatewayResolvers
+
+ {{ network.fabric ? 'fabric' : 'global' }} +
{{ network.name }}
+
+
+ {{ network.subnet }} + + + {{ network.provision_start_ip }} ··· {{ network.provision_end_ip }} + + + {{ network.gateway }} + +
+ {{ ip }} +
+
+
+ + + + +
+
+
+ + +
+
+
+
+
diff --git a/app/src/app/networking/networks/networks.component.scss b/app/src/app/networking/networks/networks.component.scss new file mode 100644 index 0000000..d2b650e --- /dev/null +++ b/app/src/app/networking/networks/networks.component.scss @@ -0,0 +1,45 @@ +.vlan-id +{ + color: #3d5e8e; +} + +h4, .network-name +{ + text-transform: uppercase; + color: #8881ff; +} + +.network-name +{ + color: #ff9c07; +} + +accordion +{ + padding-bottom: 1rem; +} + +.resolvers +{ + max-width: 300px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + .resolver + { + display: inline-block; + + + .resolver + { + padding-left: .75rem; + } + } +} + +.card .card-header +{ + position: sticky; + top: 0; + z-index: 1; +} diff --git a/app/src/app/networking/networks/networks.component.spec.ts b/app/src/app/networking/networks/networks.component.spec.ts new file mode 100644 index 0000000..62c36f5 --- /dev/null +++ b/app/src/app/networking/networks/networks.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NetworksComponent } from './networks.component'; + +describe('NetworksComponent', () => { + let component: NetworksComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NetworksComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NetworksComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/networks/networks.component.ts b/app/src/app/networking/networks/networks.component.ts new file mode 100644 index 0000000..bfe303c --- /dev/null +++ b/app/src/app/networking/networks/networks.component.ts @@ -0,0 +1,329 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { NetworkingService } from '../helpers/networking.service'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { distinctUntilChanged, first, takeUntil, debounceTime, filter, switchMap } from 'rxjs/operators'; +import { NetworkEditorComponent } from '../network-editor/network-editor.component'; +import { ToastrService } from 'ngx-toastr'; +import { VirtualNetworkEditorComponent } from '../virtual-network-editor/virtual-network-editor.component'; +import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component'; +import { Network } from '../models/network'; +import { VirtualAreaNetwork } from '../models/vlan'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import Fuse from 'fuse.js'; +import { Subject } from 'rxjs'; +import { sortArray } from '../../helpers/utils.service'; + +@Component({ + selector: 'app-networks', + templateUrl: './networks.component.html', + styleUrls: ['./networks.component.scss'] +}) +export class NetworksComponent implements OnInit, OnDestroy +{ + vlans: VirtualAreaNetwork[]; + listItems: VirtualAreaNetwork[]; + loadingIndicator = true; + editorForm: FormGroup; + + private readonly fuseJsOptions: {}; + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly networkingService: NetworkingService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService, + private readonly fb: FormBuilder) + { + this.getVlans(); + + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: true, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'name', weight: .9 }, + { name: 'networks.name', weight: .7 } + ] + }; + + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + searchTerm: [''], + sortProperty: ['name'] + }); + + this.editorForm.get('searchTerm').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('sortProperty').valueChanges + .pipe( + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private applyFiltersAndSort() + { + let listItems: VirtualAreaNetwork[] = null; + + const searchTerm = this.editorForm.get('searchTerm').value; + if (searchTerm.length >= 2) + { + const fuse = new Fuse(this.vlans, this.fuseJsOptions); + const fuseResults = fuse.search(searchTerm); + listItems = fuseResults.map(x => x.item as VirtualAreaNetwork); + } + + if (!listItems) + listItems = [...this.vlans]; + + this.listItems = sortArray(listItems, this.editorForm.get('sortProperty').value); + } + + // ---------------------------------------------------------------------------------------------------------------- + setSortProperty(propertyName: string) + { + this.editorForm.get('sortProperty').setValue(propertyName); + } + + // ---------------------------------------------------------------------------------------------------------------- + clearSearch() + { + this.editorForm.get('searchTerm').setValue(''); + } + + // ---------------------------------------------------------------------------------------------------------------- + private getVlans() + { + this.networkingService.getFabricVirtualLocalAreaNetworks() + .subscribe(x => + { + //// DEMO ONLY !!!!! + //const arr = new Array(21); + //for (let j = 0; j < 21; j++) + //{ + // const el = { ...x[0] }; + // el.name = this.dummyNames[j]; + // arr[j] = el; + //} + //// DEMO ONLY !!!!! + + this.vlans = x.map(v => + { + return v; + }).filter(n => !n.public); + + this.applyFiltersAndSort(); + + this.loadingIndicator = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + getNetworks(expanded: boolean, vlan: VirtualAreaNetwork) + { + vlan.expanded = expanded; + + if (!expanded || vlan.networks) + return; + + vlan.working = true; + + this.networkingService.getFabricNetworks(vlan.vlan_id) + .subscribe(x => + { + //// DEMO ONLY !!!!! + //const arr = new Array(21); + //for (let j = 0; j < 21; j++) + //{ + // const el = { ...x[0] }; + // el.name = this.dummyNames[j]; + // el.resolvers = this.dummyIPs; + + // const key: string = this.dummyIPs[j]; + // el.routes = { + // [key]: this.dummyIPs[j] + // }; + // arr[j] = el; + //} + //// DEMO ONLY !!!!! + + vlan.networks = x; + + vlan.working = false; + }, err => + { + this.toastr.error(`Couldn't load the networks for VLAN "${vlan.name}"`); + vlan.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + showVlanEditor(vlan?: VirtualAreaNetwork) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { vlan } + }; + + const modalRef = this.modalService.show(VirtualNetworkEditorComponent, modalConfig); + + modalRef.content.save.pipe(first()).subscribe(x => + { + const observable = vlan + ? this.networkingService.editFabricVirtualLocalAreaNetwork(x.vlan_id, x.name, x.description) + : this.networkingService.addFabricVirtualLocalAreaNetwork(x); + + observable.subscribe(response => + { + if (vlan) + { + vlan.name = x.name; + vlan.description = x.description; + } + else + this.vlans.push(response); + }, err => + { + this.toastr.error(err.error.message); + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteVlan(vlan: VirtualAreaNetwork) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${vlan.name}" VLAN?`, + confirmButtonText: 'Yes, delete this virtual network', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe(first()).subscribe(() => + { + this.networkingService.deleteFabricVirtualLocalAreaNetwork(vlan.vlan_id) + .subscribe(() => + { + const index = this.vlans.findIndex(x => x.vlan_id === vlan.vlan_id); + if (index >= 0) + { + this.vlans.splice(index, 1); + + this.applyFiltersAndSort(); + } + + this.toastr.info(`The "${vlan.name}" virtual network has been deleted`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to remove the "${vlan.name}" virtual network ${errorDetails}`); + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + showNetworkEditor(vlan: VirtualAreaNetwork, network?: Network) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { vlan, network } + }; + + const modalRef = this.modalService.show(NetworkEditorComponent, modalConfig); + modalRef.setClass('modal-lg'); + + modalRef.content.save.pipe(first()).subscribe(x => + { + if (network) + { + // Update existing entry + network.name = x.name; + network.subnet = x.subnet; + network.provision_start_ip = x.provision_start_ip; + network.provision_end_ip = x.provision_end_ip; + network.gateway = x.gateway; + network.resolvers = x.resolvers; + network.routes = x.routes; + } + else + { + vlan.networks = vlan.networks || []; + vlan.networks.push(x); + } + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteNetwork(vlan: VirtualAreaNetwork, network: Network) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${vlan.name}" > "${network.name}" network?`, + confirmButtonText: 'Yes, delete this network', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe( + first(), + switchMap(() => this.networkingService.deleteFabricNetworks(vlan.vlan_id, network.id)) + ) + .subscribe(() => + { + const index = vlan.networks.findIndex(x => x.id === network.id); + if (index >= 0) + vlan.networks.splice(index, 1); + + this.toastr.info(`The "${network.name}" network has been deleted`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to remove the "${network.name}" network ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.html b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.html new file mode 100644 index 0000000..767d117 --- /dev/null +++ b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.html @@ -0,0 +1,40 @@ +
+
+ + +
+

Virtual network editor

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.scss b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.spec.ts b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.spec.ts new file mode 100644 index 0000000..ab6e89a --- /dev/null +++ b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VirtualNetworkEditorComponent } from './virtual-network-editor.component'; + +describe('VirtualNetworkEditorComponent', () => { + let component: VirtualNetworkEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ VirtualNetworkEditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(VirtualNetworkEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.ts b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.ts new file mode 100644 index 0000000..4f8586e --- /dev/null +++ b/app/src/app/networking/virtual-network-editor/virtual-network-editor.component.ts @@ -0,0 +1,100 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { NetworkingService } from '../helpers/networking.service'; +import { ToastrService } from 'ngx-toastr'; +import { VirtualAreaNetwork } from '../models/vlan'; +import { VirtualAreaNetworkRequest } from '../models/vlan'; + +@Component({ + selector: 'app-virtual-network-editor', + templateUrl: './virtual-network-editor.component.html', + styleUrls: ['./virtual-network-editor.component.scss'] +}) +export class VirtualNetworkEditorComponent implements OnInit +{ + @Input() + vlan: VirtualAreaNetwork; + + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly networkingService: NetworkingService, + private readonly toastr: ToastrService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + id: [{ value: this.vlan?.vlan_id, disabled: !!this.vlan }, [Validators.required, Validators.min(0), Validators.max(4095)]], + name: [this.vlan?.name, Validators.required], + description: [this.vlan?.description, [Validators.maxLength(64)]] + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + + const changes = this.editorForm.getRawValue(); + + const vlan = new VirtualAreaNetworkRequest(); + vlan.name = changes.name; + vlan.description = changes.description; + vlan.vlan_id = changes.id; + + const observable = this.vlan + ? this.networkingService.editFabricVirtualLocalAreaNetwork(this.vlan.vlan_id, vlan.name, vlan.description) + : this.networkingService.addFabricVirtualLocalAreaNetwork(vlan); + + observable.subscribe(x => + { + const message = this.vlan + ? `The "${changes.name}" virtual network has been succesfully updated` + : `The "${changes.name}" virtual network has been succesfully created`; + this.toastr.info(message); + + this.working = false; + + this.save.next(x); + this.modalRef.hide(); + }, err => + { + this.toastr.error(err.error.message); + this.working = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } +} diff --git a/app/src/app/pages/dashboard/dashboard.component.html b/app/src/app/pages/dashboard/dashboard.component.html new file mode 100644 index 0000000..9c5fce9 --- /dev/null +++ b/app/src/app/pages/dashboard/dashboard.component.html @@ -0,0 +1 @@ +

dashboard works!

diff --git a/app/src/app/pages/dashboard/dashboard.component.scss b/app/src/app/pages/dashboard/dashboard.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/pages/dashboard/dashboard.component.spec.ts b/app/src/app/pages/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000..9c996c3 --- /dev/null +++ b/app/src/app/pages/dashboard/dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/pages/dashboard/dashboard.component.ts b/app/src/app/pages/dashboard/dashboard.component.ts new file mode 100644 index 0000000..843c80f --- /dev/null +++ b/app/src/app/pages/dashboard/dashboard.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'] +}) +export class DashboardComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/pages/not-found/not-found.component.html b/app/src/app/pages/not-found/not-found.component.html new file mode 100644 index 0000000..8071020 --- /dev/null +++ b/app/src/app/pages/not-found/not-found.component.html @@ -0,0 +1 @@ +

not-found works!

diff --git a/app/src/app/pages/not-found/not-found.component.scss b/app/src/app/pages/not-found/not-found.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/pages/not-found/not-found.component.spec.ts b/app/src/app/pages/not-found/not-found.component.spec.ts new file mode 100644 index 0000000..35189ed --- /dev/null +++ b/app/src/app/pages/not-found/not-found.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotFoundComponent } from './not-found.component'; + +describe('NotFoundComponent', () => { + let component: NotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NotFoundComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/pages/not-found/not-found.component.ts b/app/src/app/pages/not-found/not-found.component.ts new file mode 100644 index 0000000..7cb4124 --- /dev/null +++ b/app/src/app/pages/not-found/not-found.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-not-found', + templateUrl: './not-found.component.html', + styleUrls: ['./not-found.component.scss'] +}) +export class NotFoundComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/pages/unauthorized/unauthorized.component.html b/app/src/app/pages/unauthorized/unauthorized.component.html new file mode 100644 index 0000000..10fe24b --- /dev/null +++ b/app/src/app/pages/unauthorized/unauthorized.component.html @@ -0,0 +1 @@ +

unauthorized works!

diff --git a/app/src/app/pages/unauthorized/unauthorized.component.scss b/app/src/app/pages/unauthorized/unauthorized.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/pages/unauthorized/unauthorized.component.spec.ts b/app/src/app/pages/unauthorized/unauthorized.component.spec.ts new file mode 100644 index 0000000..28383a0 --- /dev/null +++ b/app/src/app/pages/unauthorized/unauthorized.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UnauthorizedComponent } from './unauthorized.component'; + +describe('UnauthorizedComponent', () => { + let component: UnauthorizedComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UnauthorizedComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UnauthorizedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/pages/unauthorized/unauthorized.component.ts b/app/src/app/pages/unauthorized/unauthorized.component.ts new file mode 100644 index 0000000..c25731f --- /dev/null +++ b/app/src/app/pages/unauthorized/unauthorized.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-unauthorized', + templateUrl: './unauthorized.component.html', + styleUrls: ['./unauthorized.component.scss'] +}) +export class UnauthorizedComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/pipes/file-size.pipe.spec.ts b/app/src/app/pipes/file-size.pipe.spec.ts new file mode 100644 index 0000000..8c7a39d --- /dev/null +++ b/app/src/app/pipes/file-size.pipe.spec.ts @@ -0,0 +1,8 @@ +import { FileSizePipe } from './file-size.pipe'; + +describe('FileSizePipe', () => { + it('create an instance', () => { + const pipe = new FileSizePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/app/src/app/pipes/file-size.pipe.ts b/app/src/app/pipes/file-size.pipe.ts new file mode 100644 index 0000000..b94103a --- /dev/null +++ b/app/src/app/pipes/file-size.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'fileSize' +}) +export class FileSizePipe implements PipeTransform +{ + constructor() + { + + } + + transform(bytes: any, ...args: any[]): string + { + if (!bytes) return '0 Bytes'; + + const k = 1024; + const dm = !args || args[0] < 0 ? 0 : args[0]; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i] || sizes[sizes.length - 1]}`; + } + +} diff --git a/app/src/app/security/helpers/security.service.spec.ts b/app/src/app/security/helpers/security.service.spec.ts new file mode 100644 index 0000000..949b6f8 --- /dev/null +++ b/app/src/app/security/helpers/security.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { SecurityService } from './security.service'; + +describe('SecurityService', () => { + let service: SecurityService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(SecurityService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/security/helpers/security.service.ts b/app/src/app/security/helpers/security.service.ts new file mode 100644 index 0000000..fb0dc73 --- /dev/null +++ b/app/src/app/security/helpers/security.service.ts @@ -0,0 +1,257 @@ +import { Injectable } from '@angular/core'; +import { delay, filter, first, flatMap, map, mergeMapTo, repeatWhen, switchMap, switchMapTo, take, takeUntil, tap } from 'rxjs/operators'; +import { forkJoin, Observable, of, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { User, UserRequest } from '../models/user'; +import { Role } from '../models/role'; +import { Policy } from '../models/policy'; +import { Cacheable } from 'ts-cacheable'; +import { PolicyRequest } from '../models/policy'; +import { RolePolicy } from '../models/role-policy'; +import { RoleUser } from '../models/role-user'; +import { UserResponse } from '../models/user'; + +const usersCacheBuster$ = new Subject(); +const rolesCacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class SecurityService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + // These are users (also known as sub-users); additional users who are authorized to use the same account, + // but are subject to the RBAC system. + @Cacheable({ + cacheBusterObserver: usersCacheBuster$ + }) + getUsers(): Observable + { + return this.httpClient.get(`/api/my/users`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addUser(user: UserRequest): Observable + { + return this.httpClient.post(`/api/my/users`, user) + .pipe(tap(() => usersCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + editUser(userId: string, user: UserRequest): Observable + { + return this.httpClient.post(`/api/my/users/${userId}`, user) + .pipe(tap(() => usersCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + setUserRoles(user: User, roles: Role[]) + { + const observables: Observable[] = []; + + for (const role of roles) + { + const members: RoleUser[] = []; + + const member = new RoleUser(); + member.id = user.id; + member.login = user.login; + member.type = 'subuser'; + member.default = !members.length; + + members.push(member); + + observables.push(this.httpClient.post(`/api/my/roles/${role.id}`, { name: role.name, members })); + } + + return forkJoin(observables).pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + addRoleToUser(role: Role, roleUser?: RoleUser): Observable + { + const members = role.members ? [...role.members] : []; + + if (roleUser) + { + const index = members.findIndex(m => m.id === roleUser.id); + if (index >= 0) + return of(role); + + members.push(roleUser); + } + + return this.httpClient.post(`/api/my/roles/${role.id}`, { name: role.name, members }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeRoleFromUser(role: Role, userId: string): Observable + { + if (!role.members) + return of(role); + + const members = [...role.members]; + const index = members.findIndex(r => r.id === userId); + if (index < 0) + return of(role); + + members.splice(index, 1); + + return this.httpClient.post(`/api/my/roles/${role.id}`, { name: role.name, members }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + changeUserPassword(userId: string, password: string, passwordConfirmation: string): Observable + { + return this.httpClient.post(`/api/my/users/${userId}`, + { password, password_confirmation: passwordConfirmation }); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeUser(user: User) + { + return this.httpClient.delete(`/api/my/users/${user.id}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + // Roles a sub-users can adopt when attempting to access a resource. + @Cacheable({ + cacheBusterObserver: rolesCacheBuster$ + }) + getRoles(): Observable + { + return this.httpClient.get(`/api/my/roles`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: rolesCacheBuster$ + }) + getRole(roleId: string): Observable + { + return this.httpClient.get(`/api/my/roles/${roleId}`); + } + + // ---------------------------------------------------------------------------------------------------------------- + getRoleUntil(role: Role, conditionFn: RoleCallbackFunction, callbackFn?: RoleCallbackFunction, maxRetries = 30): Observable + { + if (!conditionFn) + return of(role); + + // Keep polling the role until the expected condition is met + return this.httpClient.get(`/api/my/roles/${role.id}`) + .pipe( + tap(x => callbackFn && callbackFn(x)), + repeatWhen(x => + { + let retries = 0; + + return x.pipe( + delay(3000), + map(() => + { + if (retries++ === maxRetries) + throw { error: { message: `Failed to retrieve the current status for role "${role.name}"` } }; + }) + ); + }), + filter((x: Role) => conditionFn(x)), + take(1) // needed to stop the repeatWhen loop + ); + } + + // ---------------------------------------------------------------------------------------------------------------- + addRole(name: string): Observable + { + return this.httpClient.post(`/api/my/roles`, { name }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + editRole(roleId: string, name: string): Observable + { + return this.httpClient.post(`/api/my/roles/${roleId}`, { name }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + setRolePolicies(role: Role, policies: RolePolicy[]): Observable + { + return this.httpClient.post(`/api/my/roles/${role.id}`, { name: role.name, policies }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + addPolicyToRole(role: Role, rolePolicy?: RolePolicy): Observable + { + const policies = role.policies ? [...role.policies] : []; + + if (rolePolicy) + { + const index = policies.findIndex(r => r.id === rolePolicy.id); + if (index >= 0) + return of(role); + + policies.push(rolePolicy); + } + + return this.httpClient.post(`/api/my/roles/${role.id}`, { name: role.name, policies }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + removePolicyFromRole(policyId: string, role: Role): Observable + { + if (!role.policies) + return of(role); + + const policies = [...role.policies]; + const index = policies.findIndex(r => r.id === policyId); + if (index < 0) + return of(role); + + policies.splice(index, 1); + + return this.httpClient.post(`/api/my/roles/${role.id}`, { name: role.name, policies }) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeRole(role: Role) + { + return this.httpClient.delete(`/api/my/roles/${role.id}`) + .pipe(tap(() => rolesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + // Policies a sub-user can adopt when attempting to access a resource. + getPolicies(): Observable + { + return this.httpClient.get(`/api/my/policies`); + } + + // ---------------------------------------------------------------------------------------------------------------- + addPolicy(policy: PolicyRequest): Observable + { + return this.httpClient.post(`/api/my/policies`, policy); + } + + // ---------------------------------------------------------------------------------------------------------------- + editPolicy(policyId: string, policy: PolicyRequest): Observable + { + return this.httpClient.post(`/api/my/policies/${policyId}`, policy); + } + + // ---------------------------------------------------------------------------------------------------------------- + removePolicy(policy: Policy) + { + return this.httpClient.delete(`/api/my/policies/${policy.id}`); + } +} + +export type RoleCallbackFunction = ((role: Role) => boolean); diff --git a/app/src/app/security/models/policy.ts b/app/src/app/security/models/policy.ts new file mode 100644 index 0000000..47019d1 --- /dev/null +++ b/app/src/app/security/models/policy.ts @@ -0,0 +1,12 @@ +export class PolicyRequest +{ + name: string; + description: string; + rules: string[]; +} + + +export class Policy extends PolicyRequest +{ + id: string; +} diff --git a/app/src/app/security/models/role-policy.ts b/app/src/app/security/models/role-policy.ts new file mode 100644 index 0000000..377b395 --- /dev/null +++ b/app/src/app/security/models/role-policy.ts @@ -0,0 +1,5 @@ +export class RolePolicy +{ + id: string; + name: string; +} diff --git a/app/src/app/security/models/role-user.ts b/app/src/app/security/models/role-user.ts new file mode 100644 index 0000000..b21349e --- /dev/null +++ b/app/src/app/security/models/role-user.ts @@ -0,0 +1,7 @@ +export class RoleUser +{ + id: string; + type: string; // "subuser" or "account" + login: string; + default: boolean; +} diff --git a/app/src/app/security/models/role.ts b/app/src/app/security/models/role.ts new file mode 100644 index 0000000..e118f97 --- /dev/null +++ b/app/src/app/security/models/role.ts @@ -0,0 +1,10 @@ +import { RoleUser } from './role-user'; +import { RolePolicy } from './role-policy'; + +export class Role +{ + id: string; + name: string; + policies: RolePolicy[]; + members: RoleUser[]; +} diff --git a/app/src/app/security/models/user.ts b/app/src/app/security/models/user.ts new file mode 100644 index 0000000..ac979fe --- /dev/null +++ b/app/src/app/security/models/user.ts @@ -0,0 +1,29 @@ +import { Role } from './role'; + +export class UserRequest +{ + companyName: string; + firstName: string; + lastName: string; + login: string; + email: string; + phone: string; + address: string; + postalCode: string; + city: string; + state: string; + country: string; +} + +export class UserResponse extends UserRequest +{ + id: string; + created: Date; + updated: Date; +} + +export class User extends UserResponse +{ + roles: Role[]; + working: boolean; +} diff --git a/app/src/app/security/policy-editor/policy-editor.component.html b/app/src/app/security/policy-editor/policy-editor.component.html new file mode 100644 index 0000000..23af656 --- /dev/null +++ b/app/src/app/security/policy-editor/policy-editor.component.html @@ -0,0 +1,56 @@ +
+
+ + +
+

Policy editor

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
Rules
+
+
+
+
+
+ {{ control.value.rule }} + + +
+
+
+ + +
+
+
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/security/policy-editor/policy-editor.component.scss b/app/src/app/security/policy-editor/policy-editor.component.scss new file mode 100644 index 0000000..a512ebe --- /dev/null +++ b/app/src/app/security/policy-editor/policy-editor.component.scss @@ -0,0 +1,27 @@ +.select-list +{ + .form-check .form-check-label + { + padding: .25rem .5rem .25rem .25rem; + text-transform: uppercase; + font-family: "Bebas Neue", sans-serif; + + small + { + font-size: .7rem; + } + } + + .list-group-item + { + border: none; + background: transparent; + color: #8881ff; + padding: .5rem .5rem .5rem .75rem; + } +} + +h5 +{ + color: #ff9c07; +} diff --git a/app/src/app/security/policy-editor/policy-editor.component.spec.ts b/app/src/app/security/policy-editor/policy-editor.component.spec.ts new file mode 100644 index 0000000..4e63976 --- /dev/null +++ b/app/src/app/security/policy-editor/policy-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PolicyEditorComponent } from './policy-editor.component'; + +describe('PolicyEditorComponent', () => { + let component: PolicyEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PolicyEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PolicyEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/security/policy-editor/policy-editor.component.ts b/app/src/app/security/policy-editor/policy-editor.component.ts new file mode 100644 index 0000000..820390c --- /dev/null +++ b/app/src/app/security/policy-editor/policy-editor.component.ts @@ -0,0 +1,158 @@ +import { Component, HostListener, Input, OnInit, OnDestroy } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { Policy } from '../models/policy'; +import { SecurityService } from '../helpers/security.service'; +import { ToastrService } from 'ngx-toastr'; + +@Component({ + selector: 'app-policy-editor', + templateUrl: './policy-editor.component.html', + styleUrls: ['./policy-editor.component.scss'] +}) +export class PolicyEditorComponent implements OnInit, OnDestroy +{ + @Input() + policy: Policy; + + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + canAddRule: boolean; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly securityService: SecurityService, + private readonly toastr: ToastrService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + const rules = this.fb.array(this.policy?.rules + ? this.policy.rules.map(rule => + { + return this.fb.group( + { + rule: [rule, Validators.required] + }); + }) + : [], [Validators.required, Validators.minLength(1)] + ); + + this.canAddRule = rules.length < 4; + + this.editorForm = this.fb.group( + { + name: [this.policy?.name, Validators.required], + description: [this.policy?.description, [Validators.required, Validators.maxLength(64)]], + rules + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + addRule(rule: string) + { + const array = this.editorForm.get('rules') as FormArray; + + const control = this.fb.group( + { + rule: [rule, Validators.required] + }); + + array.push(control); + + this.canAddRule = array.length < 4; + } + + // ---------------------------------------------------------------------------------------------------------------- + removeRule(index: number) + { + const array = this.editorForm.get('rules') as FormArray; + + array.removeAt(index); + + this.canAddRule = array.length < 4; + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + removeEventListener('document:keydown.escape', this.returnPressed); + + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + + const changes = this.editorForm.getRawValue(); + + const policy = new Policy(); + policy.name = changes.name; + policy.description = changes.description; + policy.rules = changes.rules.map(x => x.rule); + + const observable = this.policy + ? this.securityService.editPolicy(this.policy.id, policy) + : this.securityService.addPolicy(policy); + + observable.subscribe(x => + { + const message = this.policy + ? `The policy "${policy.name}" has been updated` + : `The policy "${policy.name}" has been created`; + + this.toastr.info(message); + + this.working = false; + + this.save.next(x); + this.close(); + }, + err => + { + this.working = false; + this.toastr.error(err.error.message); + }); + } + + // -------------------------------------------------------------------------------------------------- + @HostListener('document:keydown.enter', ['$event']) + returnPressed(event) + { + if (!event) return; + + event.preventDefault(); + event.stopPropagation(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } + + // -------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + removeEventListener('document:keydown.escape', this.returnPressed); + } +} diff --git a/app/src/app/security/role-policies-editor/role-policies-editor.component.html b/app/src/app/security/role-policies-editor/role-policies-editor.component.html new file mode 100644 index 0000000..49f22e3 --- /dev/null +++ b/app/src/app/security/role-policies-editor/role-policies-editor.component.html @@ -0,0 +1,12 @@ +
+
+ + +
+

Assign policy to roles

+ +
+
+
diff --git a/app/src/app/security/role-policies-editor/role-policies-editor.component.scss b/app/src/app/security/role-policies-editor/role-policies-editor.component.scss new file mode 100644 index 0000000..90c8923 --- /dev/null +++ b/app/src/app/security/role-policies-editor/role-policies-editor.component.scss @@ -0,0 +1,4 @@ +fieldset +{ + height: 80vh; +} diff --git a/app/src/app/security/role-policies-editor/role-policies-editor.component.spec.ts b/app/src/app/security/role-policies-editor/role-policies-editor.component.spec.ts new file mode 100644 index 0000000..1ec459d --- /dev/null +++ b/app/src/app/security/role-policies-editor/role-policies-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RolePoliciesEditorComponent } from './role-policies-editor.component'; + +describe('RolePoliciesEditorComponent', () => { + let component: RolePoliciesEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RolePoliciesEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RolePoliciesEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/security/role-policies-editor/role-policies-editor.component.ts b/app/src/app/security/role-policies-editor/role-policies-editor.component.ts new file mode 100644 index 0000000..9804f30 --- /dev/null +++ b/app/src/app/security/role-policies-editor/role-policies-editor.component.ts @@ -0,0 +1,75 @@ +import { Component, HostListener, OnInit, OnDestroy } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-role-policies-editor', + templateUrl: './role-policies-editor.component.html', + styleUrls: ['./role-policies-editor.component.scss'] +}) +export class RolePoliciesEditorComponent implements OnInit, OnDestroy +{ + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + @HostListener('document:keydown.escape', ['$event']) + close() + { + removeEventListener('document:keydown.escape', this.close); + removeEventListener('document:keydown.return', this.saveChanges); + + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + @HostListener('document:keydown.return', ['$event']) + saveChanges() + { + console.log(this.editorForm.getRawValue()); + + this.close(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // -------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + removeEventListener('document:keydown.escape', this.close); + removeEventListener('document:keydown.return', this.saveChanges); + } +} diff --git a/app/src/app/security/security.component.html b/app/src/app/security/security.component.html new file mode 100644 index 0000000..1c1dd7e --- /dev/null +++ b/app/src/app/security/security.component.html @@ -0,0 +1,237 @@ +
+
+
+
+
+ + + + {{ 'security.policies' | translate }} + + + + + +

Policies are just access rules grouped together

+ +
    +
  • +
    + + + {{ policy.name }} + + +
    + + + +
    +
    + +
    + + {{ policy.name }} +
    + +
    {{ 'security.dropHere' | translate }}
    +
  • +
+
+
+
+
+ + + + {{ 'security.roles' | translate }} + + + + + +

To assign policies drag them over a role

+ +
    +
  • +
    +
    + + +
    + + {{ role.name }} ({{ role.policies.length }}) +
    +
    + +
    + + + +
    +
    + +
    + + {{ role.name }} +
    + +
    {{ 'security.dropHere' | translate }}
    + +
    +
      +
    • + + {{ policy.name }} +
      + +
      +
    • +
    +
    +
  • +
+
+
+
+
+ + + + {{ 'security.users' | translate }} + + + + + +

To assign roles drag them over a user

+ +
    +
  • +
    +
    + + + {{ user.login }} ({{ user.roles.length }}) +
    + +
    + + +
    +
    + +
    + + {{ user.login }} +
    + +
    {{ 'security.dropHere' | translate }}
    + +
    +
      +
    • + + {{ role.name }} +
      + +
      +
    • +
    +
    +
  • +
+ +
+
+
+
+
diff --git a/app/src/app/security/security.component.scss b/app/src/app/security/security.component.scss new file mode 100644 index 0000000..9d056db --- /dev/null +++ b/app/src/app/security/security.component.scss @@ -0,0 +1,110 @@ +fieldset +{ + background-color: rgba(16,21,39, .5); + border-radius: .3rem; + height: 100%; + transition: all 0.3s cubic-bezier(0.46, 0.03, 0.52, 0.96); + height: 100%; + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgb(18 203 240 / 40%), 0 0 10px 3px #0e162a; + } +} + +.grip +{ + cursor: move; + padding: .25rem .25rem .25rem 0; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.list-group-item +{ + background-color: transparent; + border-color: #354164; + color: #ff9c07; +} + +.list-group .list-group +{ + padding-left: 1.25rem; + + .list-group-item + { + padding-right: 0; + color: #8881ff; + } +} + +legend +{ + font-family: 'Bebas Neue', sans-serif; + line-height: 1.2; + color: #3d5e8e; + padding: .75rem .5rem .75rem 1rem; + position: relative; + background-color: rgba(16,21,39, .5); + border-radius: .3rem .3rem 0 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.cdk-drop-list +{ + &:empty:before + { + content: '\00a0'; + display: block; + border: 1px dashed #354164; + border-radius: .5rem; + padding: .5rem; + margin: .5rem; + } +} + +.cdk-drag-preview +{ + border: 1px solid #354164; + border-radius: .5rem; + padding: .5rem; + min-width: 330px; +} + +.cdk-drag-placeholder +{ + border: 1px dashed #354164; + border-radius: .5rem; + padding: .5rem; + margin: .5rem .5rem .5rem 2.75rem; +} + +.policies .cdk-drag-placeholder +{ + margin: .5rem; +} + +.cdk-drag-placeholder +{ + color: #8881ff; +} + +.cdk-drag-animating +{ + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drop-list-dragging :not(.cdk-drag-placeholder) +{ + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drop-list-receiving +{ + /*box-shadow: 0 0 3px -1px #0dcaf0;*/ +} diff --git a/app/src/app/security/security.component.spec.ts b/app/src/app/security/security.component.spec.ts new file mode 100644 index 0000000..b7f186b --- /dev/null +++ b/app/src/app/security/security.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SecurityComponent } from './security.component'; + +describe('SecurityComponent', () => { + let component: SecurityComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SecurityComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SecurityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/security/security.component.ts b/app/src/app/security/security.component.ts new file mode 100644 index 0000000..e915aac --- /dev/null +++ b/app/src/app/security/security.component.ts @@ -0,0 +1,536 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { CdkDragDrop, moveItemInArray, transferArrayItem, copyArrayItem, CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; +import { SecurityService } from './helpers/security.service'; +import { User } from './models/user'; +import { first, mergeMap, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { forkJoin, Subject } from 'rxjs'; +import { Role } from './models/role'; +import { Policy } from './models/policy'; +import { ToastrService } from 'ngx-toastr'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { UserEditorComponent } from './user-editor/user-editor.component'; +import { PolicyEditorComponent } from './policy-editor/policy-editor.component'; +import { RolePoliciesEditorComponent } from './role-policies-editor/role-policies-editor.component'; +import { UserRolesEditorComponent } from './user-roles-editor/user-roles-editor.component'; +import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component'; +import { PromptDialogComponent } from '../components/prompt-dialog/prompt-dialog.component'; +import { RolePolicy } from './models/role-policy'; +import { RoleUser } from './models/role-user'; + +@Component({ + selector: 'app-security', + templateUrl: './security.component.html', + styleUrls: ['./security.component.scss'] +}) +export class SecurityComponent implements OnInit, OnDestroy +{ + users: User[]; + roles: Role[]; + policies: Policy[]; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly securityService: SecurityService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService) + { + forkJoin({ + users: securityService.getUsers(), + roles: securityService.getRoles(), + policies: securityService.getPolicies() + }) + .subscribe(response => + { + // Roles has links to both Users and Policies + this.roles = response.roles; + + this.policies = response.policies; + + const userRoles = {}; + for (const role of response.roles) + { + for (const member of role.members) + { + userRoles[member.id] = userRoles[member.id] || []; + userRoles[member.id].push({ + id: role.id, + name: role.name + }); + } + } + + this.users = response.users.map(x => + { + let user = new User(); + user = Object.assign(user, x); + + user.roles = userRoles[x.id] || []; + + return user; + }); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + drop = (event: CdkDragDrop) => + { + if (event.previousContainer === event.container) + //moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + return; + + //copyArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); + switch (event.previousContainer.id) + { + case 'policies': + return this.addPolicyToRole(event.item.data, this.roles.find(x => x.id === event.container.data['id'])); + + case 'roles': + return this.addRoleToUser(event.item.data, this.users.find(x => x.id === event.container.data['id'])); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + usersEnterPredicate = (drag: CdkDrag, drop: CdkDropList) => + { + if (drag.dropContainer.id !== 'roles') + return false; + + const user = this.users.find(x => x.id === drop.data.id); + + return !user.roles.find(x => x.id === drag.data.id); + } + + // ---------------------------------------------------------------------------------------------------------------- + rolesEnterPredicate = (drag: CdkDrag, drop: CdkDropList) => + { + if (drag.dropContainer.id !== 'policies') + return false; + + const role = this.roles.find(x => x.id === drop.data.id); + + return !role.policies.find(x => x.id === drag.data.id); + } + + // ---------------------------------------------------------------------------------------------------------------- + noReturnPredicate(drag: CdkDrag, drop: CdkDropList) + { + return false; + } + + // ---------------------------------------------------------------------------------------------------------------- + noSortPredicate() + { + return false; + } + + // ---------------------------------------------------------------------------------------------------------------- + showUserEditor(user?: User, changePassword = false) + { + if (user) + user.working = true; + + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { user, changePassword } + }; + + const modalRef = this.modalService.show(UserEditorComponent, modalConfig); + modalRef.setClass('modal-lg'); + + modalRef.content.save.pipe(first()).subscribe(x => + { + if (changePassword) + { + this.toastr.info(`The password has been update for user "${user.login}"`); + } + else if (user) + { + this.toastr.info(`The details have been updated for user "${user.login}"`); + + user = Object.assign(user, x); + } + else + { + this.users.push(x); + + user.working = false; + } + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + showRoleEditor(role?: Role) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + value: role?.name, + required: true, + title: role ? 'Change role name' : 'Create role', + prompt: 'Type in the name for this role', + placeholder: 'Role name', + saveButtonText: role ? 'Save changes' : 'Create role' + } + }; + + const modalRef = this.modalService.show(PromptDialogComponent, modalConfig); + modalRef.content.save + .pipe( + first(), + switchMap(roleName => + { + if (role) + return this.securityService.editRole(role.id, roleName); + + return this.securityService.addRole(roleName); + }) + ) + .subscribe(x => + { + const message = role + ? `The "${role.name}" role has been renamed to "${x.name}"` + : `The "${x.name}" role has been created`; + + if (role) + { + const index = this.roles.findIndex(p => p.id === x.id); + if (index >= 0) + this.roles.splice(index, 1, x); + + // Also update the users that use this role + for (const user of this.users) + for (const userRole of user.roles) + if (role.id === userRole.id) + userRole.name = x.name; + } + else + this.roles.push(x); + + this.toastr.info(message); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteRole(role: Role) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${role.name}" role?`, + confirmButtonText: 'Yes, delete this role', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + modalRef.content.confirm.pipe( + first(), + switchMap(() => this.securityService.removeRole(role)) + ) + .subscribe(() => + { + const index = this.roles.findIndex(p => p.id === role.id); + if (index >= 0) + this.roles.splice(index, 1); + + // Also remove this role from all the associated users + for (const user of this.users) + user.roles = user.roles.filter(rp => rp.id !== role.id); + + this.toastr.info(`The "${role.name}" role has been succesfuly removed`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Faild to remove the "${role.name}" role (${errorDetails})`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + showPolicyEditor(policy?: Policy) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { policy } + }; + + const modalRef = this.modalService.show(PolicyEditorComponent, modalConfig); + + modalRef.content.save.pipe(first()).subscribe(x => + { + if (policy) + { + const index = this.policies.findIndex(p => p.id === policy.id); + if (index >= 0) + this.policies.splice(index, 1, x); + + // Also update the roles that use this policy + for (const role of this.roles) + for (const rolePolicy of role.policies) + if (policy.id === rolePolicy.id) + rolePolicy.name = x.name; + } + else + { + this.policies = this.policies || []; + this.policies.push(x); + } + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deletePolicy(policy: Policy) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete the "${policy.name}" policy?`, + confirmButtonText: 'Yes, delete this policy', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + modalRef.content.confirm.pipe( + first(), + switchMap(() => this.securityService.removePolicy(policy)) + ) + .subscribe(() => + { + const index = this.policies.findIndex(p => p.id === policy.id); + if (index >= 0) + this.policies.splice(index, 1); + + // Also remove this policy from all the associated roles + for (const role of this.roles) + role.policies = role.policies.filter(rp => rp.id !== policy.id); + + this.toastr.info(`The "${policy.name}" policy has been succesfuly removed`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Faild to remove the "${policy.name}" policy ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + assignPolicyToRoles(policy: Policy) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: {} + }; + + const modalRef = this.modalService.show(RolePoliciesEditorComponent, modalConfig); + + modalRef.content.save.pipe(first()).subscribe(x => { }); + } + + // ---------------------------------------------------------------------------------------------------------------- + addPolicyToRole(policy: Policy, role: Role) + { + const rolePolicy = new RolePolicy(); + rolePolicy.id = policy.id; + rolePolicy.name = policy.name; + + // This causes the UI to add the item in the list. In case of an error we have a compensation action + // that will remove this item from the list. We do this to avoid having the user see an empty list + // while the server responds. + role.policies = role.policies || []; + role.policies.push(rolePolicy); + + // We don't specify the second parameter of securityService.addPolicyToRole() because we've already + // updated the role's policies above + this.securityService.addPolicyToRole(role) + .subscribe(x => + { + this.toastr.info(`The "${policy.name}" policy has been added to the "${role.name}" role`); + }, err => + { + // Compensation action + const index = role.policies.findIndex(x => x.id === policy.id); + if (index >= 0) + role.policies.splice(index, 1); + + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to add the "${policy.name}" policy to the "${role.name}" role ${errorDetails}`);; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + removePolicyFromRole(policy: Policy, role: Role) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to remove the "${policy.name}" policy from the "${role.name}" role?`, + confirmButtonText: 'Yes, remove it', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + modalRef.content.confirm + .pipe( + first(), + switchMap(() => this.securityService.removePolicyFromRole(policy.id, role)) + ) + .subscribe(x => + { + const index = role.policies.findIndex(rp => rp.id === policy.id); + if (index >= 0) + { + role.policies.splice(index, 1); + + this.toastr.info(`The "${policy.name}" policy has been removed from the "${role.name}" role`); + } + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to remove the "${policy.name}" policy from the "${role.name}" role ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + addRoleToUser(role: Role, user: User) + { + const roleUser = new RoleUser(); + roleUser.id = user.id; + roleUser.type = 'subuser'; + roleUser.default = !role.members.length; + + // This causes the UI to add the item in the list. In case of an error we have a compensation action + // that will remove this item from the list. We do this to avoid having the user see an empty list + // while the server responds. + user.roles = user.roles || []; + user.roles.push(role); + + this.toastr.info(`Adding the "${role.name}" role to the "${user.login}" user...`); + + this.securityService.addRoleToUser(role, roleUser) + .pipe( + switchMap(() => this.securityService + .getRoleUntil(role, x => x.members?.some(m => m.id === roleUser.id)) + .pipe(takeUntil(this.destroy$)) + )) + .subscribe(x => + { + this.toastr.info(`The "${role.name}" role has been added to the "${user.login}" user`); + }, err => + { + // TODO: Investigate further why this method returns a 500 error, even though it succeeds + // Compensation action + const index = user.roles.findIndex(x => x.id === role.id); + if (index >= 0) + user.roles.splice(index, 1); + + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to add the "${role.name}" role to the "${user.login}" user ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeRoleFromUser(roleUser: RoleUser, user: User) + { + const role = this.roles.find(x => x.id === roleUser.id); + + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to remove the "${role.name}" role from the "${user.login}" user?`, + confirmButtonText: 'Yes, remove it', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + modalRef.content.confirm + .pipe( + first(), + tap(() => this.toastr.info(`Removing the "${role.name}" role from the "${user.login}" user...`)), + switchMap(() => this.securityService.removeRoleFromUser(role, user.id)), + switchMap(() => this.securityService + .getRoleUntil(role, x => !x.members?.some(m => m.id === roleUser.id)) + .pipe(takeUntil(this.destroy$)) + ) + ) + .subscribe(x => + { + let index = user.roles.findIndex(r => r.id === role.id); + if (index >= 0) + { + // Remove the role from the user's list of roles + user.roles.splice(index, 1); + + // Also remove the role from the list of role members + index = role.members.findIndex(rm => rm.id === user.id); + if (index >= 0) + role.members.splice(index, 1); + + this.toastr.info(`The "${role.name}" role has been removed from the "${user.login}" user`); + } + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to remove the "${role.name}" role from the "${user.login}" user ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + assignRoleToUsers(role: Role) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: {} + }; + + const modalRef = this.modalService.show(UserRolesEditorComponent, modalConfig); + + modalRef.content.save.pipe(first()).subscribe(x => { }); + } + + // ---------------------------------------------------------------------------------------------------------------- + get roleDropLists() + { + return this.roles ? new Array(this.roles.length).fill(0).map((x, i) => `role${i}`) : []; + } + + // ---------------------------------------------------------------------------------------------------------------- + get userDropLists() + { + return this.users ? new Array(this.users.length).fill(0).map((x, i) => `user${i}`) : []; + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy(): void + { + this.destroy$.next(); + } +} diff --git a/app/src/app/security/security.module.ts b/app/src/app/security/security.module.ts new file mode 100644 index 0000000..2062734 --- /dev/null +++ b/app/src/app/security/security.module.ts @@ -0,0 +1,59 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { TranslateLoader } from '@ngx-translate/core'; +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { TranslateCompiler } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { DragDropModule } from '@angular/cdk/drag-drop'; + +import { SecurityComponent } from './security.component'; +import { PolicyEditorComponent } from './policy-editor/policy-editor.component'; +import { UserEditorComponent } from './user-editor/user-editor.component'; +import { RolePoliciesEditorComponent } from './role-policies-editor/role-policies-editor.component'; +import { UserRolesEditorComponent } from './user-roles-editor/user-roles-editor.component'; + +@NgModule({ + declarations: [ + SecurityComponent, + PolicyEditorComponent, + UserEditorComponent, + RolePoliciesEditorComponent, + UserRolesEditorComponent + ], + imports: [ + SharedModule, + DragDropModule, + RouterModule.forChild([ + { + path: '', + component: SecurityComponent + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('security') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ] +}) +export class SecurityModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/app/security/user-editor/user-editor.component.html b/app/src/app/security/user-editor/user-editor.component.html new file mode 100644 index 0000000..666c679 --- /dev/null +++ b/app/src/app/security/user-editor/user-editor.component.html @@ -0,0 +1,110 @@ +
+
+ + +
+

User editor

+ +
Mandatory fields
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
Optional fields
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/security/user-editor/user-editor.component.scss b/app/src/app/security/user-editor/user-editor.component.scss new file mode 100644 index 0000000..0ea348c --- /dev/null +++ b/app/src/app/security/user-editor/user-editor.component.scss @@ -0,0 +1,4 @@ +h5 +{ + color: #ff9c07; +} diff --git a/app/src/app/security/user-editor/user-editor.component.spec.ts b/app/src/app/security/user-editor/user-editor.component.spec.ts new file mode 100644 index 0000000..156ba8d --- /dev/null +++ b/app/src/app/security/user-editor/user-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserEditorComponent } from './user-editor.component'; + +describe('UserEditorComponent', () => { + let component: UserEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/security/user-editor/user-editor.component.ts b/app/src/app/security/user-editor/user-editor.component.ts new file mode 100644 index 0000000..36d5670 --- /dev/null +++ b/app/src/app/security/user-editor/user-editor.component.ts @@ -0,0 +1,149 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray, ValidatorFn, ValidationErrors } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Observable, Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { User } from '../models/user'; +import { SecurityService } from '../helpers/security.service'; +import { UserRequest } from '../models/user'; +import { UserResponse } from '../models/user'; +import { ToastrService } from 'ngx-toastr'; + +@Component({ + selector: 'app-user-editor', + templateUrl: './user-editor.component.html', + styleUrls: ['./user-editor.component.scss'] +}) +export class UserEditorComponent implements OnInit +{ + @Input() + user: User; + + @Input() + changePassword: boolean; + + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly securityService: SecurityService, + private readonly toastr: ToastrService) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + id: [this.user?.id], + email: [this.user?.email, [Validators.required, Validators.email]], + username: [this.user?.login, Validators.required], + password: this.fb.group( + { + password: [null], + passwordCheck: [null] + }, { validators: this.passwordsValidator.bind(this) }), + firstName: [this.user?.firstName], + lastName: [this.user?.lastName], + companyName: [this.user?.companyName], + address: [this.user?.address], + postalCode: [this.user?.postalCode], + city: [this.user?.city], + state: [this.user?.state], + country: [this.user?.country], + phone: [this.user?.phone], + }); + } + + // -------------------------------------------------------------------------------------------------- + private passwordsValidator: ValidatorFn = (group: FormGroup): ValidationErrors | null => + { + if (!this.changePassword) + return null; + + const password = group.get('password').value; + + if (!password) + return { 'required': true }; + + if (password.length < 8) + return { 'passwordMinimumLengthRequired': true }; + + if (!/[0-9]/g.test(password) || !/[a-z]/g.test(password) || !/[A-Z]/g.test(password)) + return { 'passwordLowComplexity': true }; + + const passwordCheck = group.get('passwordCheck').value; + + if (passwordCheck && password !== passwordCheck) + return { 'passwordMismatch': true }; + + return null; + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + let observable: Observable; + + const changes = this.editorForm.getRawValue(); + + if (this.changePassword) + { + observable = this.securityService.changeUserPassword(this.user.id, changes.password, changes.passwordConfirmation); + } + else + { + const user = new UserRequest(); + user.login = changes.username; + user.email = changes.email; + user.companyName = changes.companyName; + user.firstName = changes.firstName; + user.lastName = changes.lastName; + user.address = changes.address; + user.postalCode = changes.postalCode; + user.city = changes.city; + user.state = changes.state; + user.country = changes.country; + user.phone = changes.phone; + + observable = this.securityService.editUser(this.user.id, user); + } + + observable.subscribe(x => + { + this.save.next(x as User); + + this.close(); + }, err => this.toastr.error(err.error.message)); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + if (!this.user) + this.changePassword = false; + + this.createForm(); + } +} diff --git a/app/src/app/security/user-roles-editor/user-roles-editor.component.html b/app/src/app/security/user-roles-editor/user-roles-editor.component.html new file mode 100644 index 0000000..3014912 --- /dev/null +++ b/app/src/app/security/user-roles-editor/user-roles-editor.component.html @@ -0,0 +1,12 @@ +
+
+ + +
+

Assign role to users

+ +
+
+
diff --git a/app/src/app/security/user-roles-editor/user-roles-editor.component.scss b/app/src/app/security/user-roles-editor/user-roles-editor.component.scss new file mode 100644 index 0000000..90c8923 --- /dev/null +++ b/app/src/app/security/user-roles-editor/user-roles-editor.component.scss @@ -0,0 +1,4 @@ +fieldset +{ + height: 80vh; +} diff --git a/app/src/app/security/user-roles-editor/user-roles-editor.component.spec.ts b/app/src/app/security/user-roles-editor/user-roles-editor.component.spec.ts new file mode 100644 index 0000000..910335a --- /dev/null +++ b/app/src/app/security/user-roles-editor/user-roles-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserRolesEditorComponent } from './user-roles-editor.component'; + +describe('UserRolesEditorComponent', () => { + let component: UserRolesEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserRolesEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserRolesEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/security/user-roles-editor/user-roles-editor.component.ts b/app/src/app/security/user-roles-editor/user-roles-editor.component.ts new file mode 100644 index 0000000..4629da9 --- /dev/null +++ b/app/src/app/security/user-roles-editor/user-roles-editor.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-user-roles-editor', + templateUrl: './user-roles-editor.component.html', + styleUrls: ['./user-roles-editor.component.scss'] +}) +export class UserRolesEditorComponent implements OnInit +{ + save = new Subject(); + loading: boolean; + working: boolean; + editorForm: FormGroup; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder) + { // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + console.log(this.editorForm.getRawValue()); + + this.close(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } +} diff --git a/app/src/app/shared.module.ts b/app/src/app/shared.module.ts new file mode 100644 index 0000000..54fd056 --- /dev/null +++ b/app/src/app/shared.module.ts @@ -0,0 +1,156 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { TabsModule } from 'ngx-bootstrap/tabs'; +import { ModalModule } from 'ngx-bootstrap/modal'; +import { ButtonsModule } from 'ngx-bootstrap/buttons'; +import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { PopoverModule } from 'ngx-bootstrap/popover'; +import { CarouselModule } from 'ngx-bootstrap/carousel'; +import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; +import { CollapseModule } from 'ngx-bootstrap/collapse'; +import { AlertModule } from 'ngx-bootstrap/alert'; +import { AccordionModule } from 'ngx-bootstrap/accordion'; + +import { ToastrModule } from 'ngx-toastr'; +import { TimeagoModule } from 'ngx-timeago'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { VirtualScrollerModule } from 'ngx-virtual-scroller'; +import { NgxSliderModule } from '@angular-slider/ngx-slider'; +import { ClipboardModule } from 'ngx-clipboard'; + +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import +{ + faHome, faFolder, faNetworkWired, faServer, faShieldAlt, faFireAlt, faProjectDiagram, faKey, faUsers, faLayerGroup, faInfoCircle, + faTimes, faTrash, faUser, faUserTag, faTag, faUserLock, faLock, faPen, faCheckCircle, faPenNib, faPlus, faMinus, faAngleRight, faHandPointer, + faArrowsAlt, faTags, faEllipsisV, faHatWizard, faUserCog, faCircle, faAngleLeft, faExternalLinkAlt, faCheck, faPowerOff, faBars, faSpinner, + faStop, faPlay, faRedo, faMicrochip, faDesktop, faCopy, faSquare, faCheckSquare, faSave, faDatabase, faClone, faSearch, faHistory, faMask, + faCloud, faCloudUploadAlt, faEye, faFingerprint, faLink, faClipboard, faCoins, faArrowRight, faEllipsisH, faStar, faCommentAlt, faOutdent +} from '@fortawesome/free-solid-svg-icons'; +import { faDocker } from '@fortawesome/free-brands-svg-icons'; + +import { AutofocusDirective } from './directives/autofocus.directive'; + +import { InlineEditorComponent } from './components/inline-editor/inline-editor.component'; +import { ImagesComponent } from './catalog/images/images.component'; +import { PackagesComponent } from './catalog/packages/packages.component'; +import { FileSizePipe } from './pipes/file-size.pipe'; +import { AutosizeModule } from 'ngx-autosize'; +import { AlphaOnlyDirective } from './directives/alpha-only.directive'; +import { ConfirmationDialogComponent } from './components/confirmation-dialog/confirmation-dialog.component'; +import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.component'; +import { CustomImageEditorComponent } from './catalog/custom-image-editor/custom-image-editor.component'; +import { LazyLoadDirective } from './directives/lazy-load.directive'; + +@NgModule({ + declarations: [ + AutofocusDirective, + //HasPermissionDirective, + InlineEditorComponent, + ImagesComponent, + PackagesComponent, + FileSizePipe, + AlphaOnlyDirective, + ConfirmationDialogComponent, + PromptDialogComponent, + CustomImageEditorComponent, + LazyLoadDirective, + ], + imports: [ + CommonModule, + RouterModule, + FormsModule, + ReactiveFormsModule, + FontAwesomeModule, + ButtonsModule.forRoot(), + CollapseModule.forRoot(), + BsDropdownModule.forRoot(), + TooltipModule.forRoot(), + PopoverModule.forRoot(), + ModalModule.forRoot(), + AlertModule.forRoot(), + CarouselModule.forRoot(), + BsDatepickerModule.forRoot(), + TabsModule.forRoot(), + AccordionModule.forRoot(), + ToastrModule.forRoot({ + timeOut: 5000, + positionClass: 'toast-bottom-center', + preventDuplicates: true, + autoDismiss: true, + maxOpened: 5, + closeButton: true, + enableHtml: true, + tapToDismiss: false + }), + TranslateModule, + TimeagoModule.forRoot(), + NgxDatatableModule, + AutosizeModule, + VirtualScrollerModule, + NgxSliderModule, + ClipboardModule + ], + exports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + FontAwesomeModule, + ButtonsModule, + CollapseModule, + BsDropdownModule, + TooltipModule, + PopoverModule, + ModalModule, + AlertModule, + CarouselModule, + BsDatepickerModule, + TabsModule, + AccordionModule, + TranslateModule, + AutofocusDirective, + TimeagoModule, + NgxDatatableModule, + ImagesComponent, + PackagesComponent, + FileSizePipe, + InlineEditorComponent, + AutosizeModule, + AlphaOnlyDirective, + ConfirmationDialogComponent, + PromptDialogComponent, + CustomImageEditorComponent, + LazyLoadDirective, + VirtualScrollerModule, + NgxSliderModule, + ClipboardModule, + //HasPermissionDirective + ], + providers: [ + FileSizePipe + ], + entryComponents: [ + + ] +}) +export class SharedModule +{ + constructor() + { + // Add an icon to the library for convenient access in other components + library.add( + faHome, faFolder, faDocker, faNetworkWired, faServer, faShieldAlt, faFireAlt, faProjectDiagram, faKey, faUsers, faLayerGroup, faInfoCircle, + faTimes, faTrash, faUser, faUserTag, faTag, faUserLock, faLock, faPen, faCheckCircle, faPenNib, faPlus, faMinus, faAngleRight, faHandPointer, + faArrowsAlt, faTags, faEllipsisV, faHatWizard, faUserCog, faCircle, faAngleLeft, faExternalLinkAlt, faCheck, faPowerOff, faBars, faSpinner, + faStop, faPlay, faRedo, faMicrochip, faDesktop, faCopy, faSquare, faCheckSquare, faSave, faDatabase, faClone, faSearch, faHistory, faMask, + faCloud, faCloudUploadAlt, faEye, faFingerprint, faLink, faClipboard, faCoins, faArrowRight, faEllipsisH, faStar, faCommentAlt, faOutdent + ); + } +} diff --git a/app/src/app/volumes/helpers/volumes.service.spec.ts b/app/src/app/volumes/helpers/volumes.service.spec.ts new file mode 100644 index 0000000..b5aa1bb --- /dev/null +++ b/app/src/app/volumes/helpers/volumes.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { VolumesService } from './volumes.service'; + +describe('VolumesService', () => { + let service: VolumesService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(VolumesService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/src/app/volumes/helpers/volumes.service.ts b/app/src/app/volumes/helpers/volumes.service.ts new file mode 100644 index 0000000..ef52c43 --- /dev/null +++ b/app/src/app/volumes/helpers/volumes.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { delay, filter, first, flatMap, map, mergeMapTo, repeatWhen, switchMap, switchMapTo, take, takeUntil, tap } from 'rxjs/operators'; +import { concat, empty, of, range, throwError, zip } from 'rxjs'; +import { Cacheable } from 'ts-cacheable'; +import { VolumeResponse } from '../models/volume'; +import { VolumeRequest } from '../models/volume'; + +export const volumesCacheBuster$ = new Subject(); + +@Injectable({ + providedIn: 'root' +}) +export class VolumesService +{ + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly httpClient: HttpClient) { } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: volumesCacheBuster$ + }) + getVolumes(): Observable + { + return this.httpClient.get(`/api/my/volumes`); + } + + // ---------------------------------------------------------------------------------------------------------------- + @Cacheable({ + cacheBusterObserver: volumesCacheBuster$ + }) + getVolume(volumeId: string): Observable + { + return this.httpClient.get(`/api/my/volumes/${volumeId}`); + } + + //// ---------------------------------------------------------------------------------------------------------------- + //@Cacheable({ + // cacheBusterObserver: volumesCacheBuster$ + //}) + //getInstanceVolumes(instanceId: string): Observable + //{ + // return this.httpClient.get(`/api/my/machines/${instanceId}/volumes`); + //} + + // ---------------------------------------------------------------------------------------------------------------- + addVolume(volume: VolumeRequest): Observable + { + return this.httpClient.post(`/api/my/volumes`, volume) + .pipe(tap(() => volumesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + // Renaming a volume is not allowed for volumes that are referenced by active VMs. => check "refs" array property + renameVolume(volumeId: string, name: string): Observable + { + return this.httpClient.post(`/api/my/volumes/${volumeId}`, { name }) + .pipe(tap(() => volumesCacheBuster$.next())); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteVolume(volume: VolumeResponse): Observable + { + return this.httpClient.delete(`/api/my/volumes/${volume.id}`) + .pipe(tap(() => volumesCacheBuster$.next())); + } +} diff --git a/app/src/app/volumes/models/volume.ts b/app/src/app/volumes/models/volume.ts new file mode 100644 index 0000000..6aabc2e --- /dev/null +++ b/app/src/app/volumes/models/volume.ts @@ -0,0 +1,26 @@ +export class VolumeRequest +{ + type = 'tritonnfs'; // currently only "tritonnfs" is supported + networks: string[]; + name: string; // <= 256 characters in length (Renaming a volume is not allowed for volumes that are referenced by active VMs. => check "refs" array property) + size: number; + tags: { ket: string; value: string }; + affinity: any[]; +} + +export class VolumeResponse extends VolumeRequest +{ + id: string; + owner_uuid: string; + state: string; + created: Date; + refs: string[]; // A list of VM UUIDs that reference this volume + filesystem_path: string; +} + +export class Volume extends VolumeResponse +{ + // mode = 'rw'; // ro || rw + // mountpoint: string; // Specifies where the volume is mounted in the newly created machine's filesystem. It must start with a slash ("/") and it must contain at least one character that is not '/'. + working: boolean; +} diff --git a/app/src/app/volumes/volume-editor/volume-editor.component.html b/app/src/app/volumes/volume-editor/volume-editor.component.html new file mode 100644 index 0000000..b9e33c2 --- /dev/null +++ b/app/src/app/volumes/volume-editor/volume-editor.component.html @@ -0,0 +1,85 @@ +
+
+ + +
+

Create volume

+ +
+
+ +
+
+
Size
+ +
+
+
Networks
+
+
+
+
+
+ {{ control.value.name }} + + +
+
+
+
+ + +
+
+
+
+
+
Tags
+
+
+
+
+
+ + {{ control.value.key }} + {{ control.value.value }} + + + + +
+
+
+ + +
+
+
+
+ +
+ + +
+
+
+
diff --git a/app/src/app/volumes/volume-editor/volume-editor.component.scss b/app/src/app/volumes/volume-editor/volume-editor.component.scss new file mode 100644 index 0000000..b87b08c --- /dev/null +++ b/app/src/app/volumes/volume-editor/volume-editor.component.scss @@ -0,0 +1,40 @@ +h5 +{ + color: #ff9c07; +} + +.select-list +{ + .form-check .form-check-label + { + padding: .25rem .5rem .25rem .25rem; + text-transform: uppercase; + font-family: "Bebas Neue", sans-serif; + + small + { + font-size: .7rem; + } + } + + .list-group-item + { + border: none; + background: transparent; + color: #8881ff; + padding: .5rem .5rem .5rem .75rem; + } + + .tag + { + color: #ff9c07; + text-transform: uppercase; + + b + { + margin-left: .5rem; + color: #8881ff; + text-transform: none; + } + } +} diff --git a/app/src/app/volumes/volume-editor/volume-editor.component.spec.ts b/app/src/app/volumes/volume-editor/volume-editor.component.spec.ts new file mode 100644 index 0000000..56ccd1f --- /dev/null +++ b/app/src/app/volumes/volume-editor/volume-editor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VolumeEditorComponent } from './volume-editor.component'; + +describe('VolumeEditorComponent', () => { + let component: VolumeEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ VolumeEditorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(VolumeEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/volumes/volume-editor/volume-editor.component.ts b/app/src/app/volumes/volume-editor/volume-editor.component.ts new file mode 100644 index 0000000..8708de6 --- /dev/null +++ b/app/src/app/volumes/volume-editor/volume-editor.component.ts @@ -0,0 +1,179 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap/modal'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { NavigationStart, Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { ToastrService } from 'ngx-toastr'; +import { VolumesService } from '../helpers/volumes.service'; +import { VolumeResponse } from '../models/volume'; +import { FileSizePipe } from '../../pipes/file-size.pipe'; +import { Network } from '../../networking/models/network'; +import { NetworkingService } from '../../networking/helpers/networking.service'; +import { LabelType, Options } from '@angular-slider/ngx-slider'; + +@Component({ + selector: 'app-volume-editor', + templateUrl: './volume-editor.component.html', + styleUrls: ['./volume-editor.component.scss'] +}) +export class VolumeEditorComponent implements OnInit +{ + save = new Subject(); + working: boolean; + editorForm: FormGroup; + networks: Network[] = []; + fabricNetworks: Network[] = []; + sizeSliderOptions: Options = { + animate: false, + stepsArray: [], + enforceStepsArray: true, + showTicks: true, + translate: this.translateBytes.bind(this) + }; + + private destroy$ = new Subject(); + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly modalRef: BsModalRef, + private readonly router: Router, + private readonly fb: FormBuilder, + private readonly volumesService: VolumesService, + private readonly networkingService: NetworkingService, + private readonly toastr: ToastrService, + private readonly fileSizePipe: FileSizePipe) + { + // When the user navigates away from this route, hide the modal + router.events + .pipe( + takeUntil(this.destroy$), + filter(e => e instanceof NavigationStart) + ) + .subscribe(() => this.modalRef.hide()); + + this.networkingService.getNetworks().subscribe(x => + { + this.networks = x; + this.fabricNetworks = x.filter(n => n.fabric); + }); + + this.sizeSliderOptions.stepsArray = [ + 10240, 20480, 30720, 40960, 51200, 61440, 71680, 81920, 92160, 102400, 204800, 307200, 409600, 512000, 614400, + 716800, 819200, 921600, 1024000 + ].map(value => ({ value })); + } + + // ---------------------------------------------------------------------------------------------------------------- + private translateBytes(value: number, label: LabelType): string + { + return this.fileSizePipe.transform(value * 1024 * 1024); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + name: [null, [Validators.required, Validators.maxLength(256)]], + type: ['tritonnfs', Validators.required], + networks: this.fb.array([], { validators: Validators.required }), + size: [10, [Validators.min(1), Validators.required]], + tags: this.fb.array([]), + affinity: this.fb.array([]) + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + close() + { + this.modalRef.hide(); + } + + // ---------------------------------------------------------------------------------------------------------------- + saveChanges() + { + this.working = true; + this.toggleSizeSlider(true); + + const changes = this.editorForm.getRawValue(); + changes.networks = changes.networks.map(x => x.id); + + this.volumesService.addVolume(changes).subscribe(x => + { + this.working = false; + this.toggleSizeSlider(); + + this.save.next(x); + this.modalRef.hide(); + }, err => + { + this.toastr.error(err.error.message); + this.working = false; + this.toggleSizeSlider(); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private toggleSizeSlider(disable = false) + { + const newOptions: Options = Object.assign({}, this.sizeSliderOptions); + newOptions.disabled = disable; + this.sizeSliderOptions = newOptions; + } + + // ---------------------------------------------------------------------------------------------------------------- + addNetwork(network: Network) + { + const array = this.editorForm.get('networks') as FormArray; + + array.push(this.fb.group( + { + id: [network.id], + name: [network.name] + })); + + this.fabricNetworks = this.fabricNetworks.filter(x => x.id !== network.id); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeNetwork(index: number) + { + const array = this.editorForm.get('networks') as FormArray; + + array.removeAt(index); + + const existingNetworks = array.getRawValue(); + this.fabricNetworks = this.networks.filter(x => !existingNetworks.find(y => y.id === x.id) && x.fabric); + } + + // ---------------------------------------------------------------------------------------------------------------- + addTag(event) + { + const array = this.editorForm.get('tags') as FormArray; + + array.push(this.fb.group({ + key: event.key, + value: event.value + })); + } + + // ---------------------------------------------------------------------------------------------------------------- + removeTag(index) + { + const array = this.editorForm.get('tags') as FormArray; + + array.removeAt(index); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + this.createForm(); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/volumes/volumes.component.html b/app/src/app/volumes/volumes.component.html new file mode 100644 index 0000000..28e3744 --- /dev/null +++ b/app/src/app/volumes/volumes.component.html @@ -0,0 +1,103 @@ +
+
+
+
+ +
+ + + + +
+ + +
+ +
+ + +
+
+
+ +
+ Loading... +
+
+ +
+
+
+

+ There are no volumes yet. +

+ + + + + + + + + + + + + + + + + + +
NameSizeNetworks
+ {{ volume.name }} + + {{ volume.size * 1024 * 1024 | fileSize}} + +
    +
  • {{ networks[network] || network }}
  • +
+
+
+ + + + +
+
+
+
+
+
diff --git a/app/src/app/volumes/volumes.component.scss b/app/src/app/volumes/volumes.component.scss new file mode 100644 index 0000000..1340341 --- /dev/null +++ b/app/src/app/volumes/volumes.component.scss @@ -0,0 +1,39 @@ +.table-responsive +{ + background-color: rgba(16, 21, 39, 0.75); + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + transition: box-shadow 0.15s ease-out; + border-radius: .25rem; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgb(18 203 240 / 40%), 0 0 10px 3px #0e162a; + } + + .highlight + { + color: #8881ff; + } + + b, .strong + { + color: #ff9c07; + font-weight: normal; + } + + .text-truncate + { + max-width: 350px; + } + + .inline-list-item + .inline-list-item + { + padding-left: .25rem; + + &:before + { + content: attr(text); + color: #3d5e8e; + } + } +} diff --git a/app/src/app/volumes/volumes.component.spec.ts b/app/src/app/volumes/volumes.component.spec.ts new file mode 100644 index 0000000..ac929cc --- /dev/null +++ b/app/src/app/volumes/volumes.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VolumesComponent } from './volumes.component'; + +describe('VolumesComponent', () => { + let component: VolumesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ VolumesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(VolumesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/volumes/volumes.component.ts b/app/src/app/volumes/volumes.component.ts new file mode 100644 index 0000000..925f9ee --- /dev/null +++ b/app/src/app/volumes/volumes.component.ts @@ -0,0 +1,254 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Volume } from './models/volume'; +import { VolumesService } from './helpers/volumes.service'; +import { InstancesService } from '../instances/helpers/instances.service'; +import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { ToastrService } from 'ngx-toastr'; +import { VolumeEditorComponent } from './volume-editor/volume-editor.component'; +import { PromptDialogComponent } from '../components/prompt-dialog/prompt-dialog.component'; +import { Subject } from 'rxjs'; +import { NetworkingService } from '../networking/helpers/networking.service'; +import { sortArray } from '../helpers/utils.service'; +import Fuse from 'fuse.js'; +import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms'; +import { distinctUntilChanged, first, takeUntil, debounceTime, filter, switchMap } from 'rxjs/operators'; + +@Component({ + selector: 'app-volumes', + templateUrl: './volumes.component.html', + styleUrls: ['./volumes.component.scss'] +}) +export class VolumesComponent implements OnInit, OnDestroy +{ + volumes: Volume[]; + listItems: Volume[]; + networks = {}; + loadingIndicator = true; + editorForm: FormGroup; + instances = {}; + + private destroy$ = new Subject(); + private readonly fuseJsOptions: {}; + + // ---------------------------------------------------------------------------------------------------------------- + constructor(private readonly volumesService: VolumesService, + private readonly networkingService: NetworkingService, + private readonly instancesService: InstancesService, + private readonly modalService: BsModalService, + private readonly toastr: ToastrService, + private readonly fb: FormBuilder) + { + + // Configure FuseJs + this.fuseJsOptions = { + includeScore: false, + minMatchCharLength: 2, + includeMatches: true, + shouldSort: false, + threshold: .3, // Lower value means a more exact search + keys: [ + { name: 'name', weight: .9 }, + { name: 'tags.key', weight: .7 } + ] + }; + + this.createForm(); + + this.instancesService.get() + .subscribe(x => + { + this.instances = x.reduce((a, b) => + { + a[b.id] = b.name; + return a; + }, {}); + }); + + this.networkingService.getNetworks().subscribe(x => + { + this.networks = x.filter(n => n.fabric).reduce((a, b) => + { + a[b.id] = b.name; + return a; + }, {}); + }); + + this.getVolumes(); + } + + // ---------------------------------------------------------------------------------------------------------------- + private createForm() + { + this.editorForm = this.fb.group( + { + searchTerm: [''], + sortProperty: ['name'] + }); + + this.editorForm.get('searchTerm').valueChanges + .pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + this.editorForm.get('sortProperty').valueChanges + .pipe( + distinctUntilChanged(), + takeUntil(this.destroy$) + ) + .subscribe(() => this.applyFiltersAndSort()); + + } + + // ---------------------------------------------------------------------------------------------------------------- + private getVolumes() + { + this.volumesService.getVolumes() + .subscribe(volumes => + { + this.volumes = volumes.map(x => x as Volume); + + this.applyFiltersAndSort(); + + this.loadingIndicator = false; + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + private applyFiltersAndSort() + { + let listItems: Volume[] = null; + + const searchTerm = this.editorForm.get('searchTerm').value; + if (searchTerm.length >= 2) + { + const fuse = new Fuse(this.volumes, this.fuseJsOptions); + const fuseResults = fuse.search(searchTerm); + listItems = fuseResults.map(x => x.item as Volume); + } + + if (!listItems) + listItems = [...this.volumes]; + + this.listItems = sortArray(listItems, this.editorForm.get('sortProperty').value); + } + + // ---------------------------------------------------------------------------------------------------------------- + setSortProperty(propertyName: string) + { + this.editorForm.get('sortProperty').setValue(propertyName); + } + + // ---------------------------------------------------------------------------------------------------------------- + clearSearch() + { + this.editorForm.get('searchTerm').setValue(''); + } + + // ---------------------------------------------------------------------------------------------------------------- + showEditor() + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: {} + }; + + const modalRef = this.modalService.show(VolumeEditorComponent, modalConfig); + modalRef.setClass('modal-lg'); + + modalRef.content.save.pipe(first()).subscribe(x => + { + this.volumes.push(x as Volume); + + this.applyFiltersAndSort(); + + this.toastr.info(`The volume "${x.name}" has been created`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + renameVolume(volume: Volume) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + value: volume.name, + required: true, + title: 'Rename volume', + prompt: 'Type in the new name for your volume', + placeholder: 'New volume name', + saveButtonText: 'Change volume name' + } + }; + + const modalRef = this.modalService.show(PromptDialogComponent, modalConfig); + + modalRef.content.save + .pipe( + switchMap(name => this.volumesService.renameVolume(volume.id, name)) + ) + .subscribe(x => + { + volume.name = x.name; + + this.toastr.info(`The volume "${volume.name}" has been renamed`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + deleteVolume(volume: Volume) + { + const modalConfig = { + ignoreBackdropClick: true, + keyboard: false, + animated: true, + initialState: { + prompt: `Are you sure you wish to permanently delete this volume?`, + confirmButtonText: 'Yes, delete it', + declineButtonText: 'No, keep it', + confirmByDefault: false + } + }; + + const modalRef = this.modalService.show(ConfirmationDialogComponent, modalConfig); + + modalRef.content.confirm.pipe( + first(), + switchMap(() => this.volumesService.deleteVolume(volume)) + ) + .subscribe(() => + { + const index = this.volumes.findIndex(x => x.id === volume.id); + if (index >= 0) + { + this.volumes.splice(index, 1); + + this.applyFiltersAndSort(); + } + + this.toastr.info(`The volume has been deleted`); + }, err => + { + const errorDetails = err.error?.message ? `(${err.error.message})` : ''; + this.toastr.error(`Failed to remove the volume ${errorDetails}`); + }); + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnInit(): void + { + } + + // ---------------------------------------------------------------------------------------------------------------- + ngOnDestroy() + { + this.destroy$.next(); + } +} diff --git a/app/src/app/volumes/volumes.module.ts b/app/src/app/volumes/volumes.module.ts new file mode 100644 index 0000000..f2ce581 --- /dev/null +++ b/app/src/app/volumes/volumes.module.ts @@ -0,0 +1,58 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared.module'; +import { RouterModule } from '@angular/router'; + +import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.service'; +import { LangChangeEvent, TranslateCompiler, TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; + +import { VolumesComponent } from './volumes.component'; +import { VolumeEditorComponent } from './volume-editor/volume-editor.component'; + + +@NgModule({ + declarations: [ + VolumesComponent, + VolumeEditorComponent + ], + imports: [ + SharedModule, + RouterModule.forChild([ + { + path: '', + component: VolumesComponent, + data: + { + title: 'volumes.title', + subTitle: 'volumes.subTitle', + icon: 'database' + } + } + ]), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + //useClass: WebpackTranslateLoader + useFactory: () => new WebpackTranslateLoader('networking') + }, + compiler: { + provide: TranslateCompiler, + useFactory: () => new TranslateMessageFormatCompiler() + }, + isolate: true + }) + ], + entryComponents: [ + VolumeEditorComponent + ] +}) +export class VolumesModule +{ + constructor(private readonly translate: TranslateService) + { + translate.use(translate.store.currentLang); + + translate.store.onLangChange.subscribe((event: LangChangeEvent) => translate.use(event.lang)); + } +} diff --git a/app/src/assets/.gitkeep b/app/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/src/assets/fonts/data-table.eot b/app/src/assets/fonts/data-table.eot new file mode 100644 index 0000000..e2f181f Binary files /dev/null and b/app/src/assets/fonts/data-table.eot differ diff --git a/app/src/assets/fonts/data-table.svg b/app/src/assets/fonts/data-table.svg new file mode 100644 index 0000000..1d7e43a --- /dev/null +++ b/app/src/assets/fonts/data-table.svg @@ -0,0 +1,26 @@ + + + +Generated by Fontastic.me + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/assets/fonts/data-table.ttf b/app/src/assets/fonts/data-table.ttf new file mode 100644 index 0000000..a86af94 Binary files /dev/null and b/app/src/assets/fonts/data-table.ttf differ diff --git a/app/src/assets/fonts/data-table.woff b/app/src/assets/fonts/data-table.woff new file mode 100644 index 0000000..84e90a6 Binary files /dev/null and b/app/src/assets/fonts/data-table.woff differ diff --git a/app/src/assets/i18n/account/en.json b/app/src/assets/i18n/account/en.json new file mode 100644 index 0000000..c7d8899 --- /dev/null +++ b/app/src/assets/i18n/account/en.json @@ -0,0 +1,5 @@ +{ + "account": + { + } +} diff --git a/app/src/assets/i18n/catalog/en.json b/app/src/assets/i18n/catalog/en.json new file mode 100644 index 0000000..f370121 --- /dev/null +++ b/app/src/assets/i18n/catalog/en.json @@ -0,0 +1,15 @@ +{ + "catalog": + { + "packages": + { + "cpu": "CPU", + "memory": "Memory", + "disk": "Storage" + }, + "custom": + { + + } + } +} diff --git a/app/src/assets/i18n/dashboard/en.json b/app/src/assets/i18n/dashboard/en.json new file mode 100644 index 0000000..04f819f --- /dev/null +++ b/app/src/assets/i18n/dashboard/en.json @@ -0,0 +1,249 @@ +{ + "dashboard": + { + "general": + { + "save": "Save changes", + "close": "Close without saving" + }, + "list": + { + "add": "Create a new machine", + "search": "Search...", + "searchTooltip": "Search by name, tag, metadata, operating system or brand", + "clearSearch": "Clear search", + "noResults": "No machine matches your filters", + "filters": "Showing {filteredCount} (out of {totalCount}: {totalRunning} running, {totalStopped} stopped)", + "filterByState": "Filter by state", + "filterByMemory": "Filter by memory", + "filterByDisk": "Filter by disk size", + "resetFilters": "Reset filters", + "showDetails": "Show machine details", + "sortBy": "Sort by {}", + "sortByName": "Name", + "sortByOs": "Operating system", + "sortByBrand": "Brand", + "sortByImage": "Image", + "sortByState": "State", + "between": "Between", + "and": "and" + }, + "listItem": + { + "infrastructureContainer": "{type} - insfrastructure container", + "virtualMachine": "{type} - virtual machine", + "stateRunning": "Running", + "stateStopping": "Stopping", + "stateProvisioning": "Provisioning", + "stateStopped": "Stopped", + "stateStarting": "Starting", + "restart": "Restart this machine", + "moreOptions": "More options", + "rename": "Rename this machine", + "editTags": "Edit machine tags", + "editMetadata": "Edit machine metadata", + "clone": "Create a machine with same specs", + "createImage": "Create an image from this machine", + "stop": "Stop this machine", + "delete": "Delete this machine", + "history": "Show machine history", + "renameTitle": "Rename machine", + "renamePrompt": "Type in the new name for your machine", + "renamePlaceholder": "New machine name", + "renameButtonText": "Change machine name", + "messages": + { + "starting": "Starting machine \"{machineName}\"...", + "started": "The machine \"{machineName}\" has been started", + "startFailed": "Failed to start the machine \"{machineName}\" {errorDetails}", + "restarting": "Restarting machine \"{machineName}\"...", + "restarted": "The machine \"{machineName}\" has been restarted", + "restartFailed": "Failed to restart the machine \"{machineName}\" {errorDetails}", + "stopping": "Stopping machine \"{machineName}\"...", + "stopped": "The machine \"{machineName}\" has been stopped", + "stopFailed": "Failed to stop the machine \"{machineName}\" {errorDetails}", + "upgrading": "Changing machine \"{machineName}\" specifications...", + "upgraded": "The machine \"{machineName}\" specifications have been changed", + "upgradeFailed": "Failed to change the specifications the machine \"{machineName}\" {errorDetails}", + "renameSameName": "You provided the same name for machine \"{machineName}\"", + "renamed": "The \"{oldMachineName}\" machine has been renamed to \"{newMachineName}\"", + "renamingFailed": "Failed to rename the \"{machineName}\" machine", + "creatingImageFromMachine": "Creating a new image based on the \"{machineName}\" machine...", + "imageCreated": "A new image \"{imageName}\" based on the \"{machineName}\" machine has been created", + "imageCreationFailed": "Failed to create an image based on the \"{machineName}\" machine {errorDetails}", + "deleteConfirmation": "Are you sure you wish to permanently delete the \"{machineName}\" machine?", + "deleting": "Removing the \"{machineName}\" machine...", + "deleted": "The \"{machineName}\" machine has been removed", + "deletingFailed": "Failed to delete the \"{machineName}\" machine", + "loadingFailed": "Failed to load additional details for the \"{machineName}\" machine" + } + }, + "infoTab": + { + "title": "Info", + "machineId": "Machine ID", + "clipboard": "Copy link to clipboard", + "preventDeletion": "Prevent this machine from being deleted", + "messages": + { + "deletionProtectionToggled": "The deletion protection for machine \"{machineName}\" is now {status}", + "deletionProtectionToggleError": "Failed to toggle the deletion protection for machine \"{machineName}\" {errorDetails}", + "copied": "The DNS address has been copied to the clipboard", + "loadError": "Couldn't load details for machine \"{machineName}\" {errorDetails}" + } + }, + "networkTab": + { + "title": "Network", + "connect": "Connect to a network", + "publicNetworks": "Public networks", + "privateNetworks": "Private networks", + "connectTo": "Connect machine to {networkName}", + "public": "Public network", + "private": "Private network", + "primary": "Primary network interface", + "ip": "IP address", + "gateway": "Gateway", + "disconnect": "Disconnect from this network", + "toggleCloudFirewall": "Toggle cloud firewall", + "confirmationTitle": "Confirm", + "messages": + { + "loadError": "Failed to load the list of available networks for machine \"{machineName}\" {errorDetails}", + "disconnectConfirmation": "Disconnecting from the \"{networkName}\" network will reboot this machine. Do you wish to continue?", + "disconnectConfirm": "Yes, disconnect from this network", + "disconnectDecline": "No, stay connected", + "connectConfirmation": "Connecting to a network will cause this machine to reboot. Do you wish to continue?", + "connectConfirm": "Yes, connect to this network", + "connectDecline": "No, don't connect", + "connecting": "Connecting machine \"{machineName}\" to the \"{networkName}\" network...", + "connected": "The machine \"{machineName}\" has been connected to the \"{networkName}\" network", + "connectError": "Failed to connect machine \"{machineName}\" to the \"{networkName}\" network {errorDetails}", + "disconnecting": "Disconnecting machine \"{machineName}\" from the \"{networkName}\" network...", + "disconnected": "The network interface has been removed from machine \"{machineName}\"", + "disconnectError": "Failed to disconnect machine \"{machineName}\" from the \"{networkName}\" network {errorDetails}", + "firewallToggled": "The cloud firewall for machine \"{machineName}\" is now {status}", + "firewallToggleError": "Failed to toggle the cloud firewall for machine \"{machineName}\" {errorDetails}" + } + }, + "snapshotsTab": + { + "title": "Snapshots", + "search": "Search by name...", + "searchTooltip": "Click to search", + "add": "Create a new snapshot", + "addPlaceholder": "New snapshot name", + "start": "Start machine from this snapshot", + "delete": "Delete this snapshot", + "restoring": "restoring", + "created": "created", + "queued": "queued", + "confirmationTitle": "Confirm", + "messages": + { + "created": "A new snapshot \"{snapshotName}\" has been created for machine \"{machineName}\"", + "creationError": "Failed to create snapshot for machine \"{machineName}\" {errorDetails}", + "restarting": "Restarting machine \"{machineName}\"", + "restartError": "Failed to restore machine \"{machineName}\" to \"{snapshotName}\" snapshot", + "confirmationTitle": "Confirmation", + "restorePrompt": "Restoring the \"{snapshotName}\" snapshot will reboot the \"{machineName}\" machine. Do you wish to continue?", + "restoreConfirm": "Yes, reboot and restore", + "restoreDecline": "No, don't restore", + "restoring": "Restoring machine \"{machineName}\" from \"{snapshotName}\" snapshot...", + "restored": "The machine \"{machineName}\" has been started from the \"{snapshotName}\" snapshot", + "deletePrompt": "Are you sure you wish to permanently delete the \"{snapshotName}\" snapshot?", + "deleteConfirm": "Yes, delete this snapshot", + "deleteDecline": "No, keep it", + "deleted": "The \"{snapshotName}\" snapshot has been deleted", + "deleteError": "Failed to delete the \"{snapshotName}\" snapshot {errorDetails}" + } + }, + "tags": + { + "title": "Machine tags", + "addHeader": "Add a new tag by specifying its key and value", + "add": "Add tag", + "remove": "Remove this tag", + "keyExists": "The key \"{key}\" is already present" + }, + "metadata": + { + "title": "Machine metadata", + "addHeader": "Add a new metadata by specifying its key and value", + "add": "Add metadata", + "remove": "Remove this metadata", + "keyExists": "The key \"{key}\" is already present" + }, + "wizard": + { + "stepImageTitle": "Image", + "stepImageDescription": "The image used to provision this machine", + "stepPackageTitle": "Image", + "stepPackageDescription": "This machine's technical specifications", + "stepSettingsTitle": "Settings", + "stepSettingsDescription": "Name your machine, configure networks and setup volumes", + "stepCreateTitle": "Create", + "stepCreateDescription": "Tag and create your machine", + "add": "Create a new machine", + "imageTypeHint": "Pick the type of machine you wish to create and the image used to provision it", + "container": "Infrastructure container", + "vm": "Virtual machine", + "docker": "Docker container", + "custom": "Custom images", + "more": "more", + "packageHint": "Choose the package that matches the technical specifications this machine will have", + "nameHint": "Give this machine a name for easier lookup", + "namePlaceholder": "Name", + "networksHint": "Which networks will this machine use?", + "noNetworks": "There are no networks configured", + "enableFirewall": "Enable the firewall for you machine", + "cloudFirewall": "Cloud firewall", + "volumesHint": "Choose the volumes you wish to mount", + "volumeName": "Volume name", + "volumeMountPoint": "Mount point", + "volumeMountPointTooltip": "Must begin with a '/' and be at least 2 characters long", + "volumeReadOnly": "Read only", + "volumesDisabled": "Volumes are disabled for KVM packages", + "noVolumes": "You don't have any volumes defined", + "tagsHint": "Tags make it easier to lookup an machine", + "addTag": "Add a new tag", + "removeTag": "Remove this tag", + "metadataHint": "Metadata makes it easier to find a machine", + "addMetadata": "Add metadata", + "removeMetadata": "Remove this metadata", + "affinityHint": "Affinity", + "affinityTip": "Affinity (aka 'locality') controls whether the machine should be placed close to or away from other machines.", + "affinityRequired": "Provision only when the affinity criteria are met", + "previousStep": "Previous step", + "createButtonText": "Create machine", + "ready": "You're about to create {imageType} having {packageDescription}, named {machineName}, based on the {imageDescription}", + "readyImageTypeContainer": "an infrastructure container", + "readyImageTypeVm": "a virtual machine", + "readyPackageDescription": "{cpu} vCPUs, {memory} RAM and {disk} storage", + "messages": + { + "creationFailed": "Failed to create machine {errorDetails}" + } + }, + "packageSelector": + { + "title": "Change machine specifications", + "subTitle": "Pick the package that best suits your requirements", + "currentPackage": "This machine's current package is", + "selectedPackage": "This package you've selected is", + "keepCurrent": "Keep current package", + "useSelected": "Use selected package" + }, + "history": + { + "title": "Machine history", + "action": "Action", + "when": "When", + "finished": "Finished", + "messages": + { + "loadFailed": "Failed to retrieve the audit log for \"{machineName}\" machine" + } + } + } +} diff --git a/app/src/assets/i18n/en.json b/app/src/assets/i18n/en.json new file mode 100644 index 0000000..2ae47e9 --- /dev/null +++ b/app/src/assets/i18n/en.json @@ -0,0 +1,77 @@ +{ + "navbar": + { + "menu": + { + "dashboard": "Dashboard", + "fileManager": "File Manager", + "volumes": "Volumes", + "customImages": "Custom Images", + "dockerImages": "Docker Images", + "dockerRegistry": "Docker Registry", + "networks": "Networks", + "virtualNetworks": "Virtual Networks", + "firewallRules": "Firewall rules", + "security": "Security", + "account": "Account" + } + }, + "account": + { + "title": "Account", + "subTitle": "Configure your account's parameters" + }, + "catalog": + { + "customImages": + { + "title": "Custom Images", + "subTitle": "" + }, + "dockerImages": + { + "title": "Docker Images", + "subTitle": "" + }, + "dockerRegistry": + { + "title": "Docker Registry", + "subTitle": "" + } + }, + "fileManager": + { + "title": "File Manager", + "subTitle": "Manage your files" + }, + "instances": + { + "title": "Dashboard", + "subTitle": "" + }, + "networks": + { + "title": "Networks", + "subTitle": "Manage your VLANs and their networks" + }, + "volumes": + { + "title": "Volumes", + "subTitle": "" + }, + "virtualNetworks": + { + "title": "Virtual Networks", + "subTitle": "Manage your virtual networks" + }, + "firewall": + { + "title": "Firewall", + "subTitle": "Configure your firewall rules" + }, + "security": + { + "title": "Security", + "subTitle": "Manage your users, roles and security policies" + } +} diff --git a/app/src/assets/i18n/networking/en.json b/app/src/assets/i18n/networking/en.json new file mode 100644 index 0000000..190a2f6 --- /dev/null +++ b/app/src/assets/i18n/networking/en.json @@ -0,0 +1,6 @@ +{ + "networking": + { + + } +} diff --git a/app/src/assets/i18n/security/en.json b/app/src/assets/i18n/security/en.json new file mode 100644 index 0000000..240e93c --- /dev/null +++ b/app/src/assets/i18n/security/en.json @@ -0,0 +1,27 @@ +{ + "security": + { + "users": "Users", + "roles": "Roles", + "policies": "Policies", + "addUser": "Add a new user", + "deleteUser": "Delete this user", + "editUser": "Update user info", + "changePassword": "Change password", + "addRole": "Add a new role", + "editRole": "Rename this role", + "deleteRole": "Delete this role", + "addPolicy": "Create a security policy", + "editPolicy": "Modify this policy", + "deletePolicy": "Delete this policy", + "toggleRoles": "Toggle user roles visibility", + "togglePolicies": "Toggle role policies visibility", + "dragRole": "Drag this role over a user to assign it", + "assignRoleToUsers": "Assign this role to multiple users", + "assignPolicyToRoles": "Assign this role to multiple roles", + "dragPolicy": "Drag this policy over a role to assign it", + "removeUserRole": "Remove this role from user", + "removeRolePolicy": "Remove this policy from role", + "dropHere": "Drop here..." + } +} diff --git a/app/src/assets/i18n/volumes/en.json b/app/src/assets/i18n/volumes/en.json new file mode 100644 index 0000000..fa9cd8b --- /dev/null +++ b/app/src/assets/i18n/volumes/en.json @@ -0,0 +1,5 @@ +{ + "volumes": + { + } +} diff --git a/app/src/assets/icons/icon-128x128.png b/app/src/assets/icons/icon-128x128.png new file mode 100644 index 0000000..6b5ab45 Binary files /dev/null and b/app/src/assets/icons/icon-128x128.png differ diff --git a/app/src/assets/icons/icon-144x144.png b/app/src/assets/icons/icon-144x144.png new file mode 100644 index 0000000..21b4f8f Binary files /dev/null and b/app/src/assets/icons/icon-144x144.png differ diff --git a/app/src/assets/icons/icon-152x152.png b/app/src/assets/icons/icon-152x152.png new file mode 100644 index 0000000..099ec77 Binary files /dev/null and b/app/src/assets/icons/icon-152x152.png differ diff --git a/app/src/assets/icons/icon-192x192.png b/app/src/assets/icons/icon-192x192.png new file mode 100644 index 0000000..17a86fa Binary files /dev/null and b/app/src/assets/icons/icon-192x192.png differ diff --git a/app/src/assets/icons/icon-384x384.png b/app/src/assets/icons/icon-384x384.png new file mode 100644 index 0000000..f71105e Binary files /dev/null and b/app/src/assets/icons/icon-384x384.png differ diff --git a/app/src/assets/icons/icon-512x512.png b/app/src/assets/icons/icon-512x512.png new file mode 100644 index 0000000..367a8b7 Binary files /dev/null and b/app/src/assets/icons/icon-512x512.png differ diff --git a/app/src/assets/icons/icon-72x72.png b/app/src/assets/icons/icon-72x72.png new file mode 100644 index 0000000..3e0a23e Binary files /dev/null and b/app/src/assets/icons/icon-72x72.png differ diff --git a/app/src/assets/icons/icon-96x96.png b/app/src/assets/icons/icon-96x96.png new file mode 100644 index 0000000..880f591 Binary files /dev/null and b/app/src/assets/icons/icon-96x96.png differ diff --git a/app/src/assets/images/bg.jpg b/app/src/assets/images/bg.jpg new file mode 100644 index 0000000..6101ebe Binary files /dev/null and b/app/src/assets/images/bg.jpg differ diff --git a/app/src/assets/images/cpu.png b/app/src/assets/images/cpu.png new file mode 100644 index 0000000..f51a548 Binary files /dev/null and b/app/src/assets/images/cpu.png differ diff --git a/app/src/assets/images/memory.png b/app/src/assets/images/memory.png new file mode 100644 index 0000000..60dc099 Binary files /dev/null and b/app/src/assets/images/memory.png differ diff --git a/app/src/assets/images/os.png b/app/src/assets/images/os.png new file mode 100644 index 0000000..b0a004b Binary files /dev/null and b/app/src/assets/images/os.png differ diff --git a/app/src/assets/images/storage.png b/app/src/assets/images/storage.png new file mode 100644 index 0000000..1f1ffac Binary files /dev/null and b/app/src/assets/images/storage.png differ diff --git a/app/src/assets/images/swap.png b/app/src/assets/images/swap.png new file mode 100644 index 0000000..3bf918e Binary files /dev/null and b/app/src/assets/images/swap.png differ diff --git a/app/src/environments/environment.prod.ts b/app/src/environments/environment.prod.ts new file mode 100644 index 0000000..c1729dd --- /dev/null +++ b/app/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + apiUrl: 'https://localhost:8443/api' +}; diff --git a/app/src/environments/environment.ts b/app/src/environments/environment.ts new file mode 100644 index 0000000..ded9c8a --- /dev/null +++ b/app/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + apiUrl: 'https://localhost:8443/api' +}; + +/* + * In development mode, to ignore zone related error stack frames such as + * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can + * import the following file, but please comment it out in production mode + * because it will have performance impact when throw error + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/app/src/index.html b/app/src/index.html new file mode 100644 index 0000000..da6e24b --- /dev/null +++ b/app/src/index.html @@ -0,0 +1,22 @@ + + + + + Manta + + + + + + + + + + + + + +
+
+ + diff --git a/app/src/karma.conf.js b/app/src/karma.conf.js new file mode 100644 index 0000000..4a9730b --- /dev/null +++ b/app/src/karma.conf.js @@ -0,0 +1,31 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/app/src/main.ts b/app/src/main.ts new file mode 100644 index 0000000..a2f708c --- /dev/null +++ b/app/src/main.ts @@ -0,0 +1,20 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +export function getBaseUrl() { + return document.getElementsByTagName('base')[0].href; +} + +const providers = [ + { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] } +]; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic(providers).bootstrapModule(AppModule) + .catch(err => console.log(err)); diff --git a/app/src/manifest.webmanifest b/app/src/manifest.webmanifest new file mode 100644 index 0000000..9b71a3f --- /dev/null +++ b/app/src/manifest.webmanifest @@ -0,0 +1,51 @@ +{ + "name": "kliniq", + "short_name": "kliniq", + "theme_color": "#1976d2", + "background_color": "#fafafa", + "display": "standalone", + "scope": "/", + "start_url": "/", + "icons": [ + { + "src": "assets/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "assets/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "assets/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "assets/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "assets/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "assets/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "assets/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "assets/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} \ No newline at end of file diff --git a/app/src/polyfills.ts b/app/src/polyfills.ts new file mode 100644 index 0000000..aa665d6 --- /dev/null +++ b/app/src/polyfills.ts @@ -0,0 +1,63 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/app/src/styles/_variables.scss b/app/src/styles/_variables.scss new file mode 100644 index 0000000..9354a37 --- /dev/null +++ b/app/src/styles/_variables.scss @@ -0,0 +1,11 @@ +$placeholder-color: #808080; +$headings-color: #2A528F; +$primary: #0A6CF5; +$secondary: #AD65E3; +$danger: #E64051; +$warning: #F2A974; +$info: #678BD5; +$success: #2AAA65; + +$success-color: #0bb13b; +$danger-color: #ff384b; diff --git a/app/src/styles/icons.scss b/app/src/styles/icons.scss new file mode 100644 index 0000000..801e728 --- /dev/null +++ b/app/src/styles/icons.scss @@ -0,0 +1,127 @@ +@charset "UTF-8"; + +@font-face +{ + font-family: 'data-table'; + src: url('../assets/fonts/data-table.eot'); + src: url('../assets/fonts/data-table.eot?#iefix') format('embedded-opentype'), + url('../assets/fonts/data-table.woff') format('woff'), + url('../assets/fonts/data-table.ttf') format('truetype'), + url('../assets/fonts/data-table.svg#data-table') format('svg'); + font-weight: normal; + font-style: normal; +} + +[data-icon]::before +{ + font-family: 'data-table' !important; + content: attr(data-icon); + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +[class^='datatable-icon-']::before, +[class*=' datatable-icon-']::before +{ + font-family: 'data-table' !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.datatable-icon-filter::before +{ + content: '\62'; +} + +.datatable-icon-collapse::before +{ + content: '\61'; +} + +.datatable-icon-expand::before +{ + content: '\63'; +} + +.datatable-icon-close::before +{ + content: '\64'; +} + +.datatable-icon-up::before +{ + content: '\65'; +} + +.datatable-icon-down::before +{ + content: '\66'; +} + +.datatable-icon-sort-unset::before +{ + content: '\63'; + opacity: 0.5; +} + +.datatable-icon-sort::before +{ + content: '\67'; +} + +.datatable-icon-done::before +{ + content: '\68'; +} + +.datatable-icon-done-all::before +{ + content: '\69'; +} + +.datatable-icon-search::before +{ + content: '\6a'; +} + +.datatable-icon-pin::before +{ + content: '\6b'; +} + +.datatable-icon-add::before +{ + content: '\6d'; +} + +.datatable-icon-left::before +{ + content: '\6f'; +} + +.datatable-icon-right::before +{ + content: '\70'; +} + +.datatable-icon-skip::before +{ + content: '\71'; +} + +.datatable-icon-prev::before +{ + content: '\72'; +} diff --git a/app/src/styles/styles.scss b/app/src/styles/styles.scss new file mode 100644 index 0000000..86acb9c --- /dev/null +++ b/app/src/styles/styles.scss @@ -0,0 +1,930 @@ +@import "_variables.scss"; + +html, body +{ + margin: 0; + width: 100%; + height: 100%; +} + +body +{ + background-color: #090b17; + color: #3d5e8e; + font-family: 'Mukta', sans-serif; + line-height: 1; +} + +body, div, virtual-scroller +{ + scrollbar-width: thin; + scrollbar-color: #ff9c07 rgba(16, 21, 39, .3); +} + +::-webkit-scrollbar +{ + height: 8px; + width: 8px; + background: rgba(16, 21, 39, .5); +} + +::-webkit-scrollbar-thumb +{ + background: #ff9c07; + border-radius: .25rem; +} + +::-webkit-scrollbar-track +{ +} + +.btn +{ + border-radius: 2rem; + line-height: 1.2; + font-family: "Bebas Neue", sans-serif; + + &.btn-info, &.btn-outline-info, &.text-info + { + &:focus + { + box-shadow: 0 0 .75rem -.1rem #00e7ff, 0 0 0.25rem #00e7ff, 0 0 3rem -1.25rem #00e7ff inset; + /*border-color: #00e7ff;*/ + } + } +} + +.btn:not(.btn-lg):not(.btn-sm) +{ + font-size: 1.2rem; + padding: .375rem .5rem .3rem; +} + +.btn-group-sm .btn:not(.btn-lg):not(.btn-sm) +{ + padding: 0 .25rem; + + &:active, &:focus + { + box-shadow: none; + } +} + +.btn.btn-lg +{ + font-size: 1.45rem; + padding-bottom: .35rem; + line-height: 1; +} + +.btn-primary +{ + background: linear-gradient(135deg, #4488c7 0%, #006ee4 100%); + color: #FFF; + text-shadow: 0px 0px 5px rgba(3,51,103,.75); +} + +.btn-success +{ + background: linear-gradient(135deg, #44bb53 0%,#169833 100%); + color: #FFF; + text-shadow: 0px 0px 5px #19752e; +} + +.btn-info +{ + color: #000; + background: linear-gradient( 135deg, #1fd1f5 0%, #00c3ea 100%); + border-color: transparent; +} + +.modal +{ + backdrop-filter: blur(6px); + z-index: 1040; +} + +.modal-backdrop +{ + background-color: rgb(1, 10, 14); +} + +.modal-content +{ + background: linear-gradient( 135deg, #00e7ff 0%, #00e7ff 100%); + box-shadow: 0rem 0rem 3rem -2rem #00e7ff; + border: none; + border-radius: .5rem; + overflow: hidden; + + .content + { + height: calc(100% - 4px); + margin: 2px; + padding: 1rem; + background: linear-gradient( 135deg, #10182b 0%, #0d1321 100%); + color: #00dcff; + border-radius: 0.35rem; + + h4 + { + color: #3d5e8e; + font-family: "Bebas Neue", sans-serif; + } + } +} + +.close +{ + position: absolute; + right: .25rem; + top: 0; + padding: .75rem 1rem; + font-weight: bold; + z-index: 1; + outline: none; + border: none; + background: none; + font-size: 2rem; + color: #3d5e8e; +} + +.ngx-datatable +{ + height: calc(100% - 1rem); + box-shadow: 0 0 0 1px #0d101d; + background-color: rgba(16, 21, 39, .5); + + .datatable-header, .datatable-body-cell + { + padding: 0 .75rem; + } + + .datatable-body + { + padding: .75rem 0; + } + + .datatable-header, .datatable-footer + { + font-family: "Bebas Neue", sans-serif; + font-variant: small-caps; + text-transform: lowercase; + font-size: 1.75rem; + color: #A3A4B8; + background-color: rgba(16, 21, 39, .5); + } + + .datatable-footer + { + padding: 0 0 0 .75rem; + + .datatable-pager + { + flex: 1 1 80%; + text-align: right; + margin: 0 10px; + + li a + { + color: #A3A4B8; + cursor: pointer; + display: inline-block; + font-family: "Bebas Neue", sans-serif; + height: 22px; + min-width: 24px; + line-height: 22px; + padding: 0 6px; + border-radius: 3px; + margin: 6px 3px; + text-align: center; + vertical-align: top; + text-decoration: none; + vertical-align: bottom; + } + + li.active a + { + color: #0e2744; + background-color: #ff9c07; + font-weight: bold; + line-height: 1; + } + + li, a + { + outline: none; + font-size: 1.5rem; + } + + .pager, li + { + padding: 0; + margin: 0; + display: inline-block; + list-style: none; + } + } + } + + .text-right + { + .datatable-header-cell-template-wrap, + .datatable-body-cell-label + { + text-align: right; + } + + .datatable-header-cell-template-wrap + { + padding-right: 1.5rem; + } + } + + .datatable-header-cell.sort-active + { + color: #ff9c07; + } + + .datatable-header .datatable-header-cell .sort-btn + { + vertical-align: sub !important; + } + + .empty-row + { + padding: 0 1rem; + font-variant: all-small-caps; + } +} + +.os +{ + background: url('../assets/images/os.png') no-repeat; + height: 16px; + width: 16px; + display: inline-block; + line-height: 1; + vertical-align: middle; + + > span + { + margin-left: 19px; + } + + &.smartos + { + background-position: 0 0; + } + + &.windows + { + background-position: -16px 0; + } + + &.linux + { + background-position: -32px 0; + } + + &.bsd + { + background-position: -48px 0; + } +} + +.tooltip-wrap .tooltip-inner +{ + white-space: pre-line; +} + +.form-floating > label +{ + padding: 1.25rem .75rem; + opacity: .65; +} + +.navbar-nav .nav-link +{ + padding-right: .5rem; + padding-left: .5rem; +} + +.text-danger +{ + color: $danger-color !important; +} + +.border-danger +{ + border-color: $danger-color !important; +} + +.text-success +{ + color: $success-color !important; +} + +.border-success +{ + border-color: $success-color !important; +} + +.badge.bg-success +{ + background-color: $success-color !important; + box-shadow: 0 0 1.5rem -0.35rem $success-color; + color: #000; +} + +.badge.bg-danger +{ + background-color: #dc3545 !important; + box-shadow: 0 0 1.5rem -0.35rem #ff384b; + color: #000; +} + +.badge.bg-light +{ + background-color: #FFF !important; + box-shadow: 0 0 1.5rem -0.35rem #FFF; + color: #000; +} + +.toast-container +{ + padding-bottom: 2rem; + + .ngx-toastr + { + box-shadow: 0 0 6px #000; + } +} + +.dashboard-tabs +{ + display: flex; + flex-direction: column; + height: 100%; + + .dashboard-tab .nav-link + { + background: none; + border-radius: 0; + border: none; + font-family: "Bebas Neue", sans-serif; + text-align: center; + color: #0dcaf0; + width: 100%; + } + + .dashboard-tab .nav-link.active + { + background: none; + border-bottom: 3px solid; + text-shadow: 0 0 0.5rem; + box-shadow: 0 -1rem 1rem -1.25rem inset; + } + + .dashboard-tab.nav-item + { + flex: 1 0; + } + + .nav-tabs + { + border: none; + } + + .tab-content + { + flex-grow: 1; + + .tab-pane + { + height: 100%; + } + } +} + +.tooltip-inner +{ + border: 1px solid #fe6daa; + background-color: #ff4d99; + color: #FFF; +} + + +.bs-tooltip-top .tooltip-arrow:before +{ + border-top-color: #fe6daa; +} + +.dropdown-menu +{ + .dropdown-item + { + padding: .5rem 1rem; + + &:focus, &:hover + { + color: #1e2125; + background-color: rgba(255,255,255,.75); + } + + &.active, &:active + { + color: #0dcaf0; + background-color: #101a30; + } + + .ng-fa-icon + { + font-size: 1rem; + vertical-align: middle; + } + } +} + +.panel +{ + + .panel + { + margin-top: 2rem; + } + + .card + { + background-color: rgba(16, 21, 39, 0.75); + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px #0b284b, 0 0 10px 3px #0e162a; + transition: box-shadow .15s ease-out; + + &:hover + { + box-shadow: 0 0 0 2px #0b2b51, 0 0 2px 4px rgba(18, 203, 240, .4), 0 0 10px 3px #0e162a; + } + + .card-header + { + background-color: rgba(16, 21, 39, 0.75); + border-bottom: none; + padding: .5rem .75rem; + } + + .card-body + { + padding: 0; + } + } +} + +.text-faded +{ + opacity: .75; +} + +.form-check-label +{ + text-transform: uppercase; + color: #0dcaf0; + font-family: "Bebas Neue", sans-serif; + font-size: 1.2rem; + vertical-align: middle; +} + +.form-switch .form-check-input +{ + background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'-4%20-4%208%208'%3E%3Ccircle%20r%3D'3'%20fill%3D'%230dcaf0'%2F%3E%3C%2Fsvg%3E"); + background-color: transparent; + border-color: #0dcaf0; + margin-top: 0; + transition: all .15s cubic-bezier(0, .8, .2, 1); + + &:focus + { + background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'-4%20-4%208%208'%3E%3Ccircle%20r%3D'3'%20fill%3D'%230dcaf0'%2F%3E%3C%2Fsvg%3E"); + } + + &:checked + { + background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'-4%20-4%208%208'%3E%3Ccircle%20r%3D'3'%20fill%3D'%2310182b'%2F%3E%3C%2Fsvg%3E"); + background-color: #0dcaf0; + box-shadow: 0 0 0 1px #10182b inset; + + &:focus + { + box-shadow: 0 0 0 1px #10182b inset, 0 0 0 0.25rem rgba(13, 110, 253, .25); + } + } +} + +.form-switch +{ + min-height: auto; +} + +:-webkit-autofill, +:-webkit-autofill:hover, +:-webkit-autofill:focus +{ + -webkit-text-fill-color: #ff9c07 !important; // Hack because color cannot be overridden + -webkit-box-shadow: 0 0 0px 1000px #0f1626 inset; // Hack because background-color cannot be overridden +} + +.form-control, .form-select +{ + border-color: rgb(0 231 255 / 75%); + background-color: transparent; + color: #ff9c07; + border-radius: 3rem; + + &:focus, &:active + { + background-color: transparent; + color: #ff9c07; + } + + &:focus + { + border-color: #00e7ff; + box-shadow: 0 0 .75rem -.1rem #00e7ff, 0 0 0.25rem #00e7ff, 0 0 .5rem -.25rem #00e7ff inset; + } + + &:disabled, &[readonly] + { + background-color: transparent; + color: rgba(255, 156, 7, .5); + border-color: rgba(0, 231, 255, .25); + } + + &::placeholder + { + color: rgba(13, 202, 240, .35); + } + + &:-ms-input-placeholder + { + color: rgba(13, 202, 240, .35); + } + + &::-ms-input-placeholder + { + color: rgba(13, 202, 240, .35); + } +} + +.form-select +{ + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%230596b2' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + + option + { + background: #11182b; + } +} + +@media (min-width: 576px) and (max-width: 992px) +{ + .modal-dialog.modal-xl + { + max-width: 762px; + } +} + +virtual-scroller +{ + flex-grow: 1; + + .scrollable-content + { + } + + &.instances .scrollable-content + { + max-width: 100%; + width: auto; + height: auto; + padding-bottom: 1rem; + display: flex; + flex-wrap: wrap; + justify-content: start; + margin-top: -1rem; + /* margin-top: calc(var(--bs-gutter-y) * -1); + margin-right: calc(var(--bs-gutter-x)/ -2); + margin-left: calc(var(--bs-gutter-x)/ -2); */ + --bs-gutter-y: 2rem; + position: relative; + + > * + { + flex-shrink: 0; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x)/ 2); + padding-left: calc(var(--bs-gutter-x)/ 2); + margin-top: var(--bs-gutter-y); + } + } +} + +@media (min-width: 576px) +{ + virtual-scroller.instances .scrollable-content + { + --bs-gutter-x: 1.5rem; + } + + .flex-grow-sm-0 + { + flex-grow: 0 !important; + } + + .w-sm-auto + { + width: auto !important; + } +} + +@media (max-width: 576px) +{ + virtual-scroller.instances .scrollable-content + { + --bs-gutter-y: 1.5rem; + } +} + +accordion +{ + .panel-heading + { + position: sticky; + top: 0; + z-index: 1010; + } +} + +.tooltip-inner +{ + max-width: 300px; +} + +.dropdown-menu, .menu-popover, .menu-dropdown +{ + background: linear-gradient(135deg, #1fd1f5 0%, #00c3ea 100%); + font-family: "Bebas Neue", sans-serif; + font-size: 1.2rem; + z-index: 1020; + max-height: 50vh; + overflow: auto; +} + +.dropdown-header +{ + color: #11182b; + opacity: .5; +} + +.menu-popover, .menu-dropdown +{ + .popover-arrow + { + display: none; + } + + .dropdown-header + { + padding: 0; + } +} + +.menu-popover +{ + .dropdown-divider + { + margin: 1rem 0; + } + + .btn + { + color: #11182b; + border-color: #11182b; + + &:hover, &:active + { + color: #10caf0; + background-color: #11182b; + } + + &:focus + { + box-shadow: 0 0 0 0.25rem rgba(11, 172, 204, .5) + } + } + + .form-control + { + border-color: #11182b; + background: #11182b; + box-shadow: 0 0 0 1px rgb(0 231 255 / 75%) inset; + padding: .5rem .75rem .375rem; + } + + .form-switch + { + display: inline-flex; + align-items: center; + color: #11182b; + + .form-check-input + { + background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'-4%20-4%208%208'%3E%3Ccircle%20r%3D'3'%20fill%3D'%2310182b'%2F%3E%3C%2Fsvg%3E"); + background-color: transparent; + border-color: #0dcaf0; + margin-top: 0; + margin-right: .5rem; + box-shadow: 0 0 0 1px #10182b inset; + + &:focus + { + box-shadow: 0 0 0 1px #10182b inset, 0 0 0 0.25rem rgba(11, 172, 204, .5); + } + + &:checked + { + background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'-4%20-4%208%208'%3E%3Ccircle%20r%3D'3'%20fill%3D'%23ff9c07'%2F%3E%3C%2Fsvg%3E"); + background-color: #10182b; + border-color: #10182b; + } + } + } + + .ngx-slider + { + .ngx-slider-pointer + { + background-color: #10182c; + + &:after + { + background: #14ccf1; + } + + &:focus + { + outline: none; + box-shadow: 0 0 0.75rem -0.1rem #00e7ff, 0 0 0.25rem #00e7ff; + } + + &.ngx-slider-active:after + { + background: #ff9c07; + } + } + + .ngx-slider-selection + { + background: #ff9c07; + } + + .ngx-slider-bubble + { + color: #11182b; + + &.ngx-slider-limit + { + color: #1d4d5d; + } + } + + .ngx-slider-tick.ngx-slider-selected + { + background: #10182c; + } + } +} + +.menu-dropdown +{ + overflow: hidden; + + .popover-content + { + padding: .5rem 0; + } +} + +.editor-form +{ + .ngx-slider + { + .ngx-slider-pointer + { + background-color: #00e7ff; + + &:after + { + background: #10182c; + } + + &:hover:after + { + background-color: #ff9c07; + } + + &:focus + { + outline: none; + box-shadow: 0 0 0.75rem -0.1rem #00e7ff, 0 0 0.25rem #00e7ff; + } + + &.ngx-slider-active:after + { + background: #ff9c07; + } + } + + .ngx-slider-selection + { + background: #ff9c07; + } + + .ngx-slider-bar, .ngx-slider-tick + { + background: #096578; + } + + .ngx-slider-bubble + { + color: #11182b; + + &.ngx-slider-model-value + { + color: #ff9c07; + } + + &.ngx-slider-limit + { + color: #1d4d5d; + } + } + } +} + +.table +{ + thead + { + font-family: "Bebas Neue", sans-serif; + font-size: 1.2rem; + color: #A3A4B8; + } + + tbody + { + color: #7dbbf1; + } + + th + { + background-color: rgba(16, 21, 39, 0.75); + padding: 1rem .5rem 1rem .75rem; + color: #3d5e8e; + border-bottom-color: transparent; + } + + td + { + border-style: none; + vertical-align: middle; + padding-left: .75rem; + color: #8881ff; + } + + &.table-hove tr:hover td + { + background-color: rgba(0, 0, 0, .5); + color: #FFF; + } +} + +.select-list +{ + overflow: auto; + height: 110px; + border-radius: .25rem; + padding: .25rem .5rem .5rem 0; + border: 1px solid rgba(0, 231, 255, 0.75); + background-color: rgba(16, 21, 39, .5); + transition: box-shadow 0.15s ease-out, border 0.15s ease-out; + + &:focus + { + border-color: #00e7ff; + box-shadow: 0 0 0.75rem -0.1rem #00e7ff, 0 0 0.25rem #00e7ff, 0 0 0.5rem -0.25rem #00e7ff inset; + outline: none; + } +} + +.no-overflow +{ + overflow: hidden; +} diff --git a/app/src/test.ts b/app/src/test.ts new file mode 100644 index 0000000..1631789 --- /dev/null +++ b/app/src/test.ts @@ -0,0 +1,20 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/app/src/tsconfig.app.json b/app/src/tsconfig.app.json new file mode 100644 index 0000000..8ea061e --- /dev/null +++ b/app/src/tsconfig.app.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": [] + }, + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/app/src/tsconfig.server.json b/app/src/tsconfig.server.json new file mode 100644 index 0000000..3f183ef --- /dev/null +++ b/app/src/tsconfig.server.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "commonjs" + }, + "angularCompilerOptions": { + "entryModule": "app/app.server.module#AppServerModule" + } +} diff --git a/app/src/tsconfig.spec.json b/app/src/tsconfig.spec.json new file mode 100644 index 0000000..de77336 --- /dev/null +++ b/app/src/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts", + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/app/src/tslint.json b/app/src/tslint.json new file mode 100644 index 0000000..52e2c1a --- /dev/null +++ b/app/src/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 0000000..b93cbfc --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "module": "esnext", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2015", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} diff --git a/app/tslint.json b/app/tslint.json new file mode 100644 index 0000000..f5f06e9 --- /dev/null +++ b/app/tslint.json @@ -0,0 +1,130 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "deprecation": { + "severity": "warn" + }, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-misused-new": true, + "no-non-null-assertion": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unnecessary-initializer": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-output-on-prefix": true, + "no-inputs-metadata-property": true, + "no-outputs-metadata-property": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-output-rename": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true + } +}