diff --git a/.gitignore b/.gitignore index 1f84d74fa6c7792dccc281707ac555755aaac259..0261a90deccd65899998610b512a0de5128f65b0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ .settings/ *.sublime-workspace + # IDE - VSCode .vscode/* !.vscode/settings.json @@ -41,3 +42,4 @@ Thumbs.db src/environments/environment.ts .gitignore + diff --git a/README.md b/README.md index 5cffeb7bfc8192bdf63ebcd2f263402cb6b0b73b..0741b6fcdaed73fe7037d31fbd0154d59cbc8134 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ # IQB Testcenter Diese Angular-Programmierung ist die clientseitige Web-Anwendung für das Online-Testen des IQB. Über diesen Weg wird die Programmierung allen Interessierten zur Verfügung gestellt. Eine Anleitung zum Installieren und Konfigurieren wird schrittweise an dieser Stelle folgen. - -# Testing - -The following technology is / will be used in the development of this project: - - diff --git a/browserslist b/browserslist new file mode 100644 index 0000000000000000000000000000000000000000..80848532e47d58cc7a4b618f600b438960f9f045 --- /dev/null +++ b/browserslist @@ -0,0 +1,12 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts.ADMIN b/e2e/src/app.e2e-spec.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..ec39f304e42ab70fc2f1ff05a9e565a7dfe54cfa --- /dev/null +++ b/e2e/src/app.e2e-spec.ts.ADMIN @@ -0,0 +1,14 @@ +import { AppPage } from './app.po'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('Welcome to itc-ng-admin!'); + }); +}); diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts.TC similarity index 100% rename from e2e/src/app.e2e-spec.ts rename to e2e/src/app.e2e-spec.ts.TC diff --git a/package-lock.json b/package-lock.json index e41c1dbff816cd0112c294e538f3d8a51d674c45..b665eda4a42200929444dece64844ac4db0ac7c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "itc-ng", - "version": "1.5.3", + "version": "2.0.0-alpha", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -12,6 +12,17 @@ "requires": { "@angular-devkit/core": "8.3.25", "rxjs": "6.4.0" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular-devkit/build-angular": { @@ -78,37 +89,20 @@ "worker-plugin": "3.2.0" }, "dependencies": { - "caniuse-lite": { - "version": "1.0.30001024", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001024.tgz", - "integrity": "sha512-LubRSEPpOlKlhZw9wGlLHo8ZVj6ugGU3xGUfLPneNBledSd9lIM5cCGZ9Mz/mMCJUhEt4jZpYteZNVRdJw5FRA==", - "dev": true - }, - "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } } } }, @@ -130,12 +124,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true - }, - "typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", - "dev": true } } }, @@ -148,6 +136,17 @@ "@angular-devkit/architect": "0.803.25", "@angular-devkit/core": "8.3.25", "rxjs": "6.4.0" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular-devkit/core": { @@ -161,6 +160,17 @@ "magic-string": "0.25.3", "rxjs": "6.4.0", "source-map": "0.7.3" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular-devkit/schematics": { @@ -171,6 +181,17 @@ "requires": { "@angular-devkit/core": "8.3.25", "rxjs": "6.4.0" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular/animations": { @@ -182,9 +203,9 @@ } }, "@angular/cdk": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-8.0.2.tgz", - "integrity": "sha512-Tv9M0vuTp7Ogk7mRiEpzBG9x5289FXe+WH0VKqN4zTzF/taTgGEuJBLDcFrwQMW0mFpGP7acVOiopgH+nClytg==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-8.2.3.tgz", + "integrity": "sha512-ZwO5Sn720RA2YvBqud0JAHkZXjmjxM0yNzCO8RVtRE9i8Gl26Wk0j0nQeJkVm4zwv2QO8MwbKUKGTMt8evsokA==", "requires": { "parse5": "^5.0.0", "tslib": "^1.7.1" @@ -224,21 +245,6 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "rimraf": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", @@ -973,12 +979,6 @@ "is-buffer": "^1.1.5" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -1052,9 +1052,9 @@ } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "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", @@ -1094,9 +1094,9 @@ "dev": true }, "@angular/material": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.0.2.tgz", - "integrity": "sha512-Q6YxX7zLsfI1kv0dSJSENSyCuwq7GdHjHzOGeogGfjQRvX3N/ty/z8YfwhQFzZ3XtIysbhuGcpAUWazgWeIKgw==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.2.3.tgz", + "integrity": "sha512-SOczkIaqes+r+9XF/UUiokidfFKBpHkOPIaFK857sFD0FBNPvPEpOr5oHKCG3feERRwAFqHS7Wo2ohVEWypb5A==", "requires": { "tslib": "^1.7.1" } @@ -1135,9 +1135,9 @@ } }, "@babel/compat-data": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", - "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.6.tgz", + "integrity": "sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q==", "dev": true, "requires": { "browserslist": "^4.8.5", @@ -1176,36 +1176,15 @@ "source-map": "^0.5.0" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", + "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", "dev": true, "requires": { - "minimist": "^1.2.0" + "minimist": "^1.2.5" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1221,12 +1200,12 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", + "integrity": "sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.7", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -1260,29 +1239,46 @@ } }, "@babel/helper-call-delegate": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz", - "integrity": "sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz", + "integrity": "sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.8.3", "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.8.7" } }, "@babel/helper-compilation-targets": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", - "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", + "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.4", - "browserslist": "^4.8.5", + "@babel/compat-data": "^7.8.6", + "browserslist": "^4.9.1", "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" }, "dependencies": { + "browserslist": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.1.tgz", + "integrity": "sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001030", + "electron-to-chromium": "^1.3.363", + "node-releases": "^1.1.50" + } + }, + "caniuse-lite": { + "version": "1.0.30001035", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz", + "integrity": "sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1292,13 +1288,14 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", - "integrity": "sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", + "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", "dev": true, "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.6.0" + "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { @@ -1370,16 +1367,17 @@ } }, "@babel/helper-module-transforms": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz", - "integrity": "sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz", + "integrity": "sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", "@babel/helper-simple-access": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.8.6", "lodash": "^4.17.13" } }, @@ -1421,15 +1419,15 @@ } }, "@babel/helper-replace-supers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz", - "integrity": "sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.8.3", "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/helper-simple-access": { @@ -1472,64 +1470,6 @@ "@babel/template": "^7.8.3", "@babel/traverse": "^7.8.4", "@babel/types": "^7.8.3" - }, - "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" - } - }, - "@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/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" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "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": { @@ -1544,9 +1484,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.8.tgz", + "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -1621,12 +1561,12 @@ } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz", - "integrity": "sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", + "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-create-regexp-features-plugin": "^7.8.8", "@babel/helper-plugin-utils": "^7.8.3" } }, @@ -1742,9 +1682,9 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz", - "integrity": "sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz", + "integrity": "sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.8.3", @@ -1752,7 +1692,7 @@ "@babel/helper-function-name": "^7.8.3", "@babel/helper-optimise-call-expression": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", "@babel/helper-split-export-declaration": "^7.8.3", "globals": "^11.1.0" } @@ -1767,9 +1707,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz", - "integrity": "sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", + "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -1805,9 +1745,9 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", - "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz", + "integrity": "sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -1915,12 +1855,12 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz", - "integrity": "sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.8.tgz", + "integrity": "sha512-hC4Ld/Ulpf1psQciWWwdnUspQoQco2bMzSrwU6TmzRlvoYQe4rQFy9vnCZDTlVeCQj0JPfL+1RX0V8hCJvkgBA==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.8.3", + "@babel/helper-call-delegate": "^7.8.7", "@babel/helper-get-function-arity": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3" } @@ -1935,12 +1875,12 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz", - "integrity": "sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", + "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", "dev": true, "requires": { - "regenerator-transform": "^0.14.0" + "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { @@ -2082,55 +2022,55 @@ } } }, + "@babel/runtime": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.6", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "@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==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2155,6 +2095,17 @@ "rxjs": "6.4.0", "tree-kill": "1.2.2", "webpack-sources": "1.4.3" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@schematics/angular": { @@ -2181,22 +2132,35 @@ "rxjs": "6.4.0", "semver": "6.3.0", "semver-intersect": "1.4.0" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, - "@testim/chrome-version": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.0.7.tgz", - "integrity": "sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw==" - }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/file-saver": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz", + "integrity": "sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==" }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, "requires": { "@types/events": "*", "@types/minimatch": "*", @@ -2204,9 +2168,9 @@ } }, "@types/jasmine": { - "version": "2.8.16", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.16.tgz", - "integrity": "sha512-056oRlBBp7MDzr+HoU5su099s/s7wjZ3KcHxLfv+Byqb9MwdLUvsfLgw1VS97hsh3ddxSPyQu+olHMnoVTUY6g==", + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.9.tgz", + "integrity": "sha512-KNL2Fq6GRmty2j6+ZmueT/Z/dkctLNH+5DFoGHNDtcgt7yME9NZd8x2p81Yuea1Xux/qAryDd3zVLUoKpDz1TA==", "dev": true }, "@types/jasminewd2": { @@ -2221,12 +2185,14 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true }, "@types/node": { - "version": "8.9.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", - "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==" + "version": "13.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.1.tgz", + "integrity": "sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==", + "dev": true }, "@types/q": { "version": "0.0.32", @@ -2235,9 +2201,9 @@ "dev": true }, "@types/selenium-webdriver": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", - "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", + "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", "dev": true }, "@types/source-list-map": { @@ -2480,9 +2446,9 @@ } }, "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, "adm-zip": { @@ -2519,6 +2485,7 @@ "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -2551,12 +2518,12 @@ "dev": true }, "ansi-escapes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", - "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "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.8.1" + "type-fest": "^0.11.0" } }, "ansi-html": { @@ -2611,6 +2578,12 @@ "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==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2658,6 +2631,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, "requires": { "array-uniq": "^1.0.1" } @@ -2665,7 +2639,8 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true }, "array-unique": { "version": "0.3.2", @@ -2695,6 +2670,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -2740,7 +2716,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assign-symbols": { "version": "1.0.0", @@ -2778,7 +2755,8 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "atob": { "version": "2.1.2", @@ -2804,12 +2782,14 @@ "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=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "dev": true }, "axobject-query": { "version": "2.0.2", @@ -2820,50 +2800,6 @@ "ast-types-flow": "0.0.7" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "babel-plugin-dynamic-import-node": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", @@ -2882,7 +2818,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -2967,6 +2904,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -3015,14 +2953,6 @@ "dev": true, "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "bluebird": { @@ -3061,6 +2991,21 @@ "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", @@ -3087,6 +3032,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3182,6 +3128,7 @@ "version": "4.8.6", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "dev": true, "requires": { "caniuse-lite": "^1.0.30001023", "electron-to-chromium": "^1.3.341", @@ -3233,7 +3180,8 @@ "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==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "buffer-indexof": { "version": "1.1.1", @@ -3348,9 +3296,10 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001027", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz", - "integrity": "sha512-7xvKeErvXZFtUItTHgNtLgS9RJpVnwBlWX8jSo/BO8VsF6deszemZSkJJJA1KOKrXuzZH4WALpAJdq5EyfgMLg==" + "version": "1.0.30001024", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001024.tgz", + "integrity": "sha512-LubRSEPpOlKlhZw9wGlLHo8ZVj6ugGU3xGUfLPneNBledSd9lIM5cCGZ9Mz/mMCJUhEt4jZpYteZNVRdJw5FRA==", + "dev": true }, "canonical-path": { "version": "1.0.0", @@ -3361,7 +3310,8 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "chalk": { "version": "2.4.2", @@ -3408,9 +3358,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "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": { @@ -3422,19 +3372,6 @@ "tslib": "^1.9.0" } }, - "chromedriver": { - "version": "79.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-79.0.3.tgz", - "integrity": "sha512-XkgXrYF+M1oAT02aIIEjaM4x0oaUXTBoB+PvCNdh3rKUhn596byCc6Jy3USutZj4/0R/K+Bqf4I+4RESvfjehg==", - "requires": { - "@testim/chrome-version": "^1.0.7", - "del": "^4.1.1", - "extract-zip": "^1.6.7", - "mkdirp": "^0.5.1", - "request": "^2.88.0", - "tcp-port-used": "^1.0.1" - } - }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -3474,11 +3411,6 @@ } } }, - "classlist.js": { - "version": "1.1.20150312", - "resolved": "https://registry.npmjs.org/classlist.js/-/classlist.js-1.1.20150312.tgz", - "integrity": "sha1-HXCEL3Ai8I2awIbOaeWyUPLFd4k=" - }, "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", @@ -3628,6 +3560,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -3645,9 +3578,9 @@ "dev": true }, "compare-versions": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.1.tgz", - "integrity": "sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==", + "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": { @@ -3690,17 +3623,36 @@ "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=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "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", @@ -3718,6 +3670,23 @@ "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": { @@ -3851,9 +3820,9 @@ } }, "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==" + "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.6.4", @@ -3876,7 +3845,8 @@ "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=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cosmiconfig": { "version": "5.2.1", @@ -3903,13 +3873,31 @@ "schema-utils": "^2.6.1" }, "dependencies": { + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "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" + } + }, + "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==", + "dev": true + }, "schema-utils": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", - "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", "dev": true, "requires": { - "ajv": "^6.10.2", + "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } } @@ -3999,48 +3987,14 @@ "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==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", + "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", "dev": true, "requires": { - "cssesc": "^0.1.0", - "fastparse": "^1.1.1", - "regexpu-core": "^1.0.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "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" - } - } + "cssesc": "^3.0.0", + "fastparse": "^1.1.2", + "regexpu-core": "^4.6.0" } }, "cssauron": { @@ -4053,9 +4007,9 @@ } }, "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, "custom-event": { @@ -4071,15 +4025,16 @@ "dev": true }, "damerau-levenshtein": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", - "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", + "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=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -4091,11 +4046,12 @@ "dev": true }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "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.0.0" + "ms": "^2.1.1" } }, "debuglog": { @@ -4130,11 +4086,6 @@ "regexp.prototype.flags": "^1.2.0" } }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -4208,6 +4159,7 @@ "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", @@ -4222,6 +4174,7 @@ "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", @@ -4233,7 +4186,8 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true } } } @@ -4242,7 +4196,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "depd": { "version": "1.1.2", @@ -4295,9 +4250,9 @@ "dev": true }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "diffie-hellman": { @@ -4379,6 +4334,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -4391,9 +4347,10 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.348", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.348.tgz", - "integrity": "sha512-6O0IInybavGdYtcbI4ryF/9e3Qi8/soi6C68ELRseJuTwQPKq39uGgVVeQHG28t69Sgsky09nXBRhUiFXsZyFQ==" + "version": "1.3.377", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.377.tgz", + "integrity": "sha512-cm2WzMKf/3dW5+hNANKm8GAW6SwIWOqLTJ6GPCD0Bbw1qJ9Wzm9nmx9M+byzSsgw8CdCv5fb/wzLFqVS5h6QrA==", + "dev": true }, "elliptic": { "version": "6.5.2", @@ -4475,6 +4432,12 @@ "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", @@ -4522,6 +4485,12 @@ "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", @@ -4756,6 +4725,15 @@ "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", @@ -4773,6 +4751,12 @@ "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 } } }, @@ -4820,6 +4804,21 @@ "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", @@ -4831,7 +4830,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { "version": "3.0.2", @@ -4930,31 +4930,23 @@ } } }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fastparse": { "version": "1.1.2", @@ -4971,14 +4963,6 @@ "websocket-driver": ">=0.5.1" } }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "requires": { - "pend": "~1.2.0" - } - }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -4986,9 +4970,9 @@ "dev": true }, "figures": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", - "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "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" @@ -5004,18 +4988,41 @@ "schema-utils": "^2.0.0" }, "dependencies": { + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "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" + } + }, + "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==", + "dev": true + }, "schema-utils": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", - "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", "dev": true, "requires": { - "ajv": "^6.10.2", + "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } } } }, + "file-saver": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", + "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -5055,6 +5062,23 @@ "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": { @@ -5088,9 +5112,9 @@ } }, "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { "semver": "^6.0.0" @@ -5148,9 +5172,9 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz", + "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==", "dev": true, "requires": { "debug": "^3.0.0" @@ -5164,12 +5188,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -5182,12 +5200,14 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "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==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -5225,15 +5245,6 @@ "readable-stream": "^2.0.0" } }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -5269,7 +5280,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.1.2", @@ -5321,14 +5333,16 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "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==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5393,6 +5407,11 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", @@ -5402,12 +5421,14 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "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==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -5550,9 +5571,9 @@ } }, "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==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "hpack.js": { @@ -5647,6 +5668,12 @@ "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 } } }, @@ -5666,6 +5693,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -5696,12 +5724,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -5823,6 +5845,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5831,7 +5854,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "1.3.5", @@ -5947,12 +5971,13 @@ "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true }, "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "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 }, "iqb-components": { @@ -6109,12 +6134,14 @@ "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==" + "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" } @@ -6123,6 +6150,7 @@ "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" } @@ -6175,12 +6203,8 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, "is-windows": { "version": "1.0.2", @@ -6194,20 +6218,11 @@ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, - "is2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", - "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", - "requires": { - "deep-is": "^0.1.3", - "ip-regex": "^2.1.0", - "is-url": "^1.2.2" - } - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isbinaryfile": { "version": "3.0.3", @@ -6233,7 +6248,8 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "istanbul-api": { "version": "2.1.6", @@ -6350,27 +6366,12 @@ "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 }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6408,9 +6409,9 @@ } }, "jasmine-core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.3.0.tgz", - "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==", + "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": { @@ -6468,7 +6469,8 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "jsesc": { "version": "2.5.2", @@ -6485,17 +6487,20 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true }, "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==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "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=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json3": { "version": "3.3.3", @@ -6510,14 +6515,6 @@ "dev": true, "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "jsonfile": { @@ -6539,6 +6536,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -6607,12 +6605,11 @@ } }, "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "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": { - "fs-access": "^1.0.0", "which": "^1.2.1" } }, @@ -6627,26 +6624,18 @@ } }, "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==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.1.1.tgz", + "integrity": "sha512-pxBmv5K7IkBRLsFSTOpgiK/HzicQT3mfFF+oHAC7nxMfYKhaYFgxOa5qjnHW4sL5rUnmdkSajoudOnnOdPyW4Q==", "dev": true, "requires": { - "jasmine-core": "^3.3" - }, - "dependencies": { - "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-core": "^3.5.0" } }, "karma-jasmine-html-reporter": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.4.2.tgz", - "integrity": "sha512-7g0gPj8+9JepCNJR9WjDyQ2RkZ375jpdurYQyAYv8PorUCadepl8vrD6LmMqOGcM17cnrynBawQYZHaumgDjBw==", + "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": { @@ -6665,9 +6654,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "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 }, "lcid": { @@ -6800,23 +6789,6 @@ "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" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "loglevel": { @@ -6871,9 +6843,9 @@ } }, "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, "make-fetch-happen": { @@ -6925,11 +6897,6 @@ "object-visit": "^1.0.0" } }, - "material-design-icons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", - "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" - }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -7127,12 +7094,14 @@ "mime-db": { "version": "1.43.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true }, "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==", + "dev": true, "requires": { "mime-db": "1.43.0" } @@ -7171,14 +7140,16 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "minipass": { "version": "2.9.0", @@ -7242,8 +7213,17 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } } }, "move-concurrently": { @@ -7261,9 +7241,10 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "multicast-dns": { "version": "6.2.3", @@ -7332,9 +7313,9 @@ "dev": true }, "node-fetch-npm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.3.tgz", + "integrity": "sha512-DgwoKEsqLnFZtk3ap7GWBHcHwnUhsNmQqEDcdjfQ8GofLEFJ081NAd4Uin3R7RFZBWVJCwHISw1oaEqPgSLloA==", "dev": true, "requires": { "encoding": "^0.1.11", @@ -7388,9 +7369,10 @@ } }, "node-releases": { - "version": "1.1.48", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.48.tgz", - "integrity": "sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw==", + "version": "1.1.52", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.52.tgz", + "integrity": "sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ==", + "dev": true, "requires": { "semver": "^6.3.0" } @@ -7505,9 +7487,9 @@ } }, "npm-registry-fetch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz", - "integrity": "sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz", + "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -7536,12 +7518,6 @@ "path-key": "^2.0.0" } }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -7557,12 +7533,14 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-component": { "version": "0.0.3", @@ -7684,6 +7662,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -7723,6 +7702,14 @@ "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=", + "dev": true + } } }, "original": { @@ -7812,7 +7799,8 @@ "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true }, "p-retry": { "version": "3.0.1", @@ -7982,12 +7970,14 @@ "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=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "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=" + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true }, "path-key": { "version": "2.0.1", @@ -8037,15 +8027,11 @@ "sha.js": "^2.4.8" } }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true }, "picomatch": { "version": "2.2.1", @@ -8056,17 +8042,20 @@ "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -8099,12 +8088,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -8185,9 +8168,9 @@ } }, "postcss-value-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", - "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", + "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", "dev": true }, "prepend-http": { @@ -8211,7 +8194,8 @@ "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==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "promise": { "version": "7.3.1", @@ -8351,12 +8335,6 @@ "path-is-inside": "^1.0.1" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -8412,13 +8390,13 @@ } }, "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "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.0" + "ipaddr.js": "1.9.1" } }, "prr": { @@ -8436,7 +8414,8 @@ "psl": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "dev": true }, "public-encrypt": { "version": "4.0.3", @@ -8488,7 +8467,8 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "q": { "version": "1.4.1", @@ -8505,7 +8485,8 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "query-string": { "version": "4.3.4", @@ -8590,13 +8571,31 @@ "schema-utils": "^2.0.1" }, "dependencies": { + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "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" + } + }, + "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==", + "dev": true + }, "schema-utils": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", - "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", "dev": true, "requires": { - "ajv": "^6.10.2", + "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } } @@ -8647,6 +8646,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8691,9 +8691,9 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", - "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "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" @@ -8706,12 +8706,13 @@ "dev": true }, "regenerator-transform": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", - "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.3.tgz", + "integrity": "sha512-zXHNKJspmONxBViAb3ZUmFoFPnTBs3zFhCEZJiwp/gkNzxVbTqNJVjYKx6Qk1tQ1P4XLf4TbH9+KBB7wGoAaUw==", "dev": true, "requires": { - "private": "^0.1.6" + "@babel/runtime": "^7.8.4", + "private": "^0.1.8" } }, "regex-not": { @@ -8735,17 +8736,17 @@ } }, "regexpu-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", - "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", "dev": true, "requires": { "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.1.0", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.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.1.0" + "unicode-match-property-value-ecmascript": "^1.2.0" } }, "regjsgen": { @@ -8755,9 +8756,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz", - "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==", + "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" @@ -8790,9 +8791,10 @@ "dev": true }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -8801,7 +8803,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -8811,7 +8813,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } @@ -8835,9 +8837,9 @@ "dev": true }, "resolve": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", - "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -8896,6 +8898,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -8911,9 +8914,9 @@ } }, "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", "dev": true, "requires": { "is-promise": "^2.1.0" @@ -8929,9 +8932,9 @@ } }, "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "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" } @@ -8939,7 +8942,8 @@ "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==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safe-regex": { "version": "1.1.0", @@ -8953,7 +8957,8 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sass": { "version": "1.22.9", @@ -9052,7 +9057,8 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "semver-dsl": { "version": "1.0.1", @@ -9109,6 +9115,23 @@ "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", @@ -9138,6 +9161,15 @@ "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", @@ -9156,6 +9188,12 @@ "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", @@ -9291,6 +9329,15 @@ "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", @@ -9309,6 +9356,12 @@ "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", @@ -9410,6 +9463,12 @@ "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 } } }, @@ -9455,6 +9514,12 @@ "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 } } }, @@ -9489,6 +9554,12 @@ "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 } } }, @@ -9533,12 +9604,6 @@ "requires": { "websocket-driver": ">=0.5.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -9642,9 +9707,9 @@ "dev": true }, "sourcemap-codec": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.7.tgz", - "integrity": "sha512-RuN23NzhAOuUtaivhcrjXx1OPXsFeH9m5sI373/U7+tGLKihjUyboZAzOadytMjnqHp1f45RGk1IzDKCpDpSYA==", + "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": { @@ -9690,23 +9755,6 @@ "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "spdy-transport": { @@ -9723,25 +9771,10 @@ "wbuf": "^1.7.3" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "readable-stream": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", - "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", + "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", @@ -9779,6 +9812,7 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -9887,12 +9921,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -9953,6 +9981,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9988,13 +10017,31 @@ "schema-utils": "^2.0.1" }, "dependencies": { + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "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" + } + }, + "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==", + "dev": true + }, "schema-utils": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", - "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", "dev": true, "requires": { - "ajv": "^6.10.2", + "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } } @@ -10086,30 +10133,6 @@ "yallist": "^3.0.3" } }, - "tcp-port-used": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", - "requires": { - "debug": "4.1.0", - "is2": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "terser": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz", @@ -10271,19 +10294,13 @@ "dev": true }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tree-kill": { @@ -10293,52 +10310,42 @@ "dev": true }, "ts-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", - "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", + "integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==", "dev": true, "requires": { - "arrify": "^1.0.0", - "buffer-from": "^1.1.0", - "diff": "^3.1.0", + "arg": "^4.1.0", + "diff": "^4.0.1", "make-error": "^1.1.1", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", "source-map-support": "^0.5.6", - "yn": "^2.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "yn": "3.1.1" } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" }, "tslint": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", - "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", - "diff": "^3.2.0", + "diff": "^4.0.1", "glob": "^7.1.1", - "js-yaml": "^3.7.0", + "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.27.2" + "tsutils": "^2.29.0" }, "dependencies": { "semver": { @@ -10368,6 +10375,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -10375,12 +10383,13 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "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": { @@ -10396,19 +10405,15 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, - "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==" - }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -10432,15 +10437,15 @@ } }, "unicode-match-property-value-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", - "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "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.0.5", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", - "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "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": { @@ -10492,12 +10497,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -10563,6 +10562,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -10655,7 +10655,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util-promisify": { "version": "2.1.0", @@ -10675,7 +10676,8 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -10706,6 +10708,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -11438,11 +11441,6 @@ "minimalistic-assert": "^1.0.0" } }, - "web-animations-js": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.2.tgz", - "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==" - }, "webdriver-js-extender": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", @@ -11638,15 +11636,6 @@ "upath": "^1.1.1" } }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -12253,12 +12242,6 @@ "is-buffer": "^1.1.5" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -12434,7 +12417,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "ws": { "version": "6.2.1", @@ -12523,14 +12507,6 @@ "decamelize": "^1.2.0" } }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "requires": { - "fd-slicer": "~1.0.1" - } - }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", @@ -12538,9 +12514,9 @@ "dev": true }, "yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, "zone.js": { diff --git a/package.json b/package.json index 55c168b5fa11cec254601c2d582f0fa7ff9d7f1a..1f6188e3d4c7686aeaa577d4c40e969b23f47605 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "itc-ng", - "version": "1.5.3", + "version": "2.0.0-alpha", "scripts": { "ng": "ng", "start": "ng serve", @@ -11,50 +11,46 @@ }, "private": true, "dependencies": { - "@angular/animations": "^8.0.0", - "@angular/cdk": "~8.0.0", - "@angular/common": "^8.0.0", - "@angular/compiler": "^8.0.0", - "@angular/core": "^8.0.0", + "@angular/animations": "~8.2.14", + "@angular/cdk": "~8.2.3", + "@angular/common": "~8.2.14", + "@angular/compiler": "~8.2.14", + "@angular/core": "~8.2.14", "@angular/flex-layout": "~8.0.0-beta.27", - "@angular/forms": "^8.0.0", - "@angular/material": "~8.0.0", - "@angular/platform-browser": "^8.0.0", - "@angular/platform-browser-dynamic": "^8.0.0", - "@angular/router": "^8.0.0", - "browserslist": "^4.8.6", - "caniuse-lite": "^1.0.30001027", - "chromedriver": "^79.0.3", - "classlist.js": "^1.1.20150312", - "core-js": "~2.6.11", - "iqb-components": "^1.6.4", - "material-design-icons": "~3.0.1", - "rxjs": "~6.4.0", - "tslib": "~1.9.0", - "ua-parser-js": "^0.7.21", - "web-animations-js": "^2.3.2", + "@angular/forms": "~8.2.14", + "@angular/material": "~8.2.3", + "@angular/platform-browser": "~8.2.14", + "@angular/platform-browser-dynamic": "~8.2.14", + "@angular/router": "~8.2.14", + "@types/file-saver": "^2.0.1", + "iqb-components": "1.6.x", + "core-js": "^3.6.4", + "file-saver": "^2.0.2", + "hammerjs": "^2.0.8", + "rxjs": "^6.5.4", + "tslib": "^1.10.0", "zone.js": "~0.9.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.803.25", - "@angular/cli": "^8.3.25", - "@angular/compiler-cli": "^8.0.0", - "@angular/language-service": "^8.0.0", - "@types/jasmine": "~2.8.8", + "@angular-devkit/build-angular": "~0.803.23", + "@angular/cli": "~8.3.23", + "@angular/compiler-cli": "~8.2.14", + "@angular/language-service": "~8.2.14", + "@types/jasmine": "^3.5.1", "@types/jasminewd2": "~2.0.8", - "@types/node": "~8.9.4", - "codelyzer": "~5.2.1", - "jasmine-core": "~3.3", + "@types/node": "^13.5.0", + "codelyzer": "^5.0.1", + "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~4.2.1", - "karma": "^4.4.1", - "karma-chrome-launcher": "~2.2.0", + "karma": "~4.4.1", + "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "^2.1.1", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "~1.4.2", - "protractor": "^5.4.3", - "ts-node": "~7.0.0 ", - "tslint": "~5.11.0", - "typescript": "~3.4.0-rc" + "karma-jasmine": "~3.1.0", + "karma-jasmine-html-reporter": "~1.5.1", + "protractor": "^5.4.2", + "ts-node": "~8.6.2", + "tslint": "~5.20.1", + "typescript": "~3.5.0" }, "resolutions": { "tree-kill": "1.2.2" diff --git a/src/app/about/about.component.css b/src/app/about/about.component.css new file mode 100644 index 0000000000000000000000000000000000000000..5b0d7dccc5371ab0350d50bf7bfddadcc41c2715 --- /dev/null +++ b/src/app/about/about.component.css @@ -0,0 +1,7 @@ +.mat-card { + margin: 10px; +} + +.status { + background-color: lightgrey; +} diff --git a/src/app/about/about.component.html.ADMIN b/src/app/about/about.component.html.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..86c3a142ed6ac57a8879a60a34be0401993d50f0 --- /dev/null +++ b/src/app/about/about.component.html.ADMIN @@ -0,0 +1,49 @@ +<div class="logo"> + <a [routerLink]="['/']"> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + </a> +</div> +<div class="page-body"> + <div fxLayout="row wrap" fxLayoutAlign="center stretch" style="padding: 30px;"> + <mat-card fxFlex="0 2 500px"> + <mat-card-title>IQB-Testcenter Verwaltung - Impressum/Datenschutz</mat-card-title> + + <!-- - - - - - - - - - - - - - - - - --> + <mat-card-content> + <p>Das <a href="http://www.iqb.hu-berlin.de" target="_blank">Institut zur Qualitätsentwicklung im Bildungswesen</a> + betreibt auf diesen Seiten eine Pilotanwendung für das computerbasierte Leistungstesten von + Schülerinnen und Schülern. Dies ist die Web-Anwendung zur Verwaltung der Testinhalte und -ergebnisse. + Der Zugang ist nur möglich, wenn Sie vom IQB + Zugangsdaten erhalten haben. Es sind keine weiteren Seiten öffentlich verfügbar.</p> + + <ul> + <li>Programmname: {{ appName }}</li> + <li>Programmversion: {{ appVersion }}</li> + <li>Copyright: {{ appPublisher }}</li> + </ul> + + <p> + <em>Postanschrift:</em><br/> + Humboldt-Universität zu Berlin<br/> + Institut zur Qualitätsentwicklung im Bildungswesen<br/> + Unter den Linden 6<br/> + 10099 Berlin</p> + <p> + <em>Sitz:</em><br/> + Luisenstr. 56<br/> + 10117 Berlin<br/> + Tel: +49 [30] 2093 - 46500 (Zentrale)<br/> + Fax: +49 [30] 2093 - 46599<br/> + E-Mail: <a href="mailto:iqboffice@iqb.hu-berlin.de">iqboffice@iqb.hu-berlin.de</a> + </p> + <p> + <em>Name und Anschrift der Datenschutzbeauftragten</em><br/> + Frau Gesine Hoffmann-Holland<br/> + Tel: +49 (30) 2093-2591<br/> + E-Mail: datenschutz@uv.hu-berlin.de<br/> + <a href="http://www.hu-berlin.de/de/datenschutz" target="_blank">www.hu-berlin.de/de/datenschutz</a> + </p> + </mat-card-content> + </mat-card> + </div> +</div> diff --git a/src/app/about/about.component.html b/src/app/about/about.component.html.TC similarity index 100% rename from src/app/about/about.component.html rename to src/app/about/about.component.html.TC diff --git a/src/app/about/about.component.ts.ADMIN b/src/app/about/about.component.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..6c45d5de93054072f89c091fc0d81a5fe643aa3d --- /dev/null +++ b/src/app/about/about.component.ts.ADMIN @@ -0,0 +1,13 @@ +import { Component, Inject } from '@angular/core'; + +@Component({ + templateUrl: './about.component.html', + styleUrls: ['./about.component.css'] +}) +export class AboutComponent { + constructor( + @Inject('APP_NAME') public appName: string, + @Inject('APP_PUBLISHER') public appPublisher: string, + @Inject('APP_VERSION') public appVersion: string + ) { } +} diff --git a/src/app/about/about.component.ts b/src/app/about/about.component.ts.TC similarity index 100% rename from src/app/about/about.component.ts rename to src/app/about/about.component.ts.TC diff --git a/src/app/app-routing.module.ts.ADMIN b/src/app/app-routing.module.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..055813bfcfbafed9ff15ba133be39bdd1ca6d252 --- /dev/null +++ b/src/app/app-routing.module.ts.ADMIN @@ -0,0 +1,21 @@ +import { AboutComponent } from './about/about.component'; +import { StartComponent } from './start/start.component'; +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { WorkspaceComponent } from './workspace'; + + +const routes: Routes = [ + {path: '', redirectTo: 'start', pathMatch: 'full'}, + {path: 'start', component: StartComponent}, + {path: 'about', component: AboutComponent}, + // {path: 'ws', loadChildren: './workspace/workspace.module#WorkspaceModule'}, + {path: 'ws', component: WorkspaceComponent}, + {path: 'superadmin', loadChildren: './superadmin/superadmin.module#SuperadminModule'} +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts.TC similarity index 100% rename from src/app/app-routing.module.ts rename to src/app/app-routing.module.ts.TC diff --git a/src/app/app.component.scss.ADMIN b/src/app/app.component.scss.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..cc857e228cbb688b27fcf764f225a3172e695599 --- /dev/null +++ b/src/app/app.component.scss.ADMIN @@ -0,0 +1,37 @@ +@import '~@angular/material/theming'; +@import '../iqb-theme2.scss'; + +.itp-fill-remaining-space { + flex: 1 1 auto; +} + +.mat-toolbar { + overflow: auto; + position: absolute; + width: 100%; + height: 70px; + font-family: inherit; + /* background-color: #003333; */ + background: linear-gradient(to left, #003333, #045659, #0d7b84, #1aa2b2, #2acae5); + color: white; +} + +mat-toolbar > span { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.logo { + width: 80px; + margin-right: 20px; + margin-top: 12px; +} + +.material-icons { + font-size: 2.0rem; +} + +.mat-button { + text-align: right; +} diff --git a/src/app/app.component.scss b/src/app/app.component.scss.TC similarity index 100% rename from src/app/app.component.scss rename to src/app/app.component.scss.TC diff --git a/src/app/app.component.spec.ts.ADMIN b/src/app/app.component.spec.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..1bd39e806dcbb5d54e5864e713d07289f50abe45 --- /dev/null +++ b/src/app/app.component.spec.ts.ADMIN @@ -0,0 +1,31 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + it(`should have as title 'app'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app'); + })); + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to itc-ng-admin!'); + })); +}); diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts.TC similarity index 100% rename from src/app/app.component.spec.ts rename to src/app/app.component.spec.ts.TC diff --git a/src/app/app.component.ts.ADMIN b/src/app/app.component.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..3a8614b65e365fca8386a504ede395333e7c5f3b --- /dev/null +++ b/src/app/app.component.ts.ADMIN @@ -0,0 +1,40 @@ +import { LoginData } from './app.interfaces'; +import { BackendService } from './backend.service'; +import { ServerError } from 'iqb-components'; +import { Component, OnInit } from '@angular/core'; +import { MainDataService } from './maindata.service'; + +@Component({ + selector: 'app-root', + template: `<router-outlet></router-outlet>`, + styleUrls: ['./app.component.scss'] +}) + + +export class AppComponent implements OnInit { + + constructor ( + private mds: MainDataService, + private bs: BackendService) { } + + ngOnInit() { + const adminToken = localStorage.getItem('at'); + if (adminToken !== null) { + if (adminToken.length > 0) { + this.bs.getLoginData(adminToken).subscribe( + (admindata: LoginData) => { + this.mds.setNewLoginData(admindata); + }, (err: ServerError) => { + this.mds.setNewLoginData(); + console.log(err); + this.mds.globalErrorMsg$.next(err); + } + ); + } else { + this.mds.setNewLoginData(); + } + } else { + this.mds.setNewLoginData(); + } + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts.TC similarity index 100% rename from src/app/app.component.ts rename to src/app/app.component.ts.TC diff --git a/src/app/app.interceptor.ts.ADMIN b/src/app/app.interceptor.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..2e8cf43af2fa2b39043a9fce57d554fe1db1d479 --- /dev/null +++ b/src/app/app.interceptor.ts.ADMIN @@ -0,0 +1,29 @@ +import { MainDataService } from './maindata.service'; +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() + +export class AuthInterceptor implements HttpInterceptor { + constructor(public mds: MainDataService) {} + + intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + const loginData = this.mds.loginData$.getValue(); + if (loginData !== null) { + const authDataStr = request.headers.get('AuthToken'); + let authData = {}; + if (authDataStr) { + authData = JSON.parse(authDataStr); + } + authData['at'] = loginData.admintoken; + return next.handle(request.clone({ + setHeaders: { + AuthToken: JSON.stringify(authData) + } + })); + } else { + return next.handle(request); + } + } +} diff --git a/src/app/app.interceptor.ts b/src/app/app.interceptor.ts.TC similarity index 100% rename from src/app/app.interceptor.ts rename to src/app/app.interceptor.ts.TC diff --git a/src/app/app.interfaces.ts.ADMIN b/src/app/app.interfaces.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..2b1568e07e768d6139e7620a32d1c47f053bb274 --- /dev/null +++ b/src/app/app.interfaces.ts.ADMIN @@ -0,0 +1,12 @@ +export interface WorkspaceData { + id: number; + name: string; + role: string; +} + +export interface LoginData { + admintoken: string; + name: string; + workspaces: WorkspaceData[]; + is_superadmin: boolean; +} diff --git a/src/app/app.interfaces.ts b/src/app/app.interfaces.ts.TC similarity index 100% rename from src/app/app.interfaces.ts rename to src/app/app.interfaces.ts.TC diff --git a/src/app/app.module.ts.ADMIN b/src/app/app.module.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..f418079254ed4676d2dbc52feeaf8e3cdc16679a --- /dev/null +++ b/src/app/app.module.ts.ADMIN @@ -0,0 +1,76 @@ +import { AboutComponent } from './about/about.component'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; +import { NgModule} from '@angular/core'; +import { LocationStrategy, HashLocationStrategy } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { BackendService } from './backend.service'; + +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; + +import { StartComponent } from './start/start.component'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { IqbComponentsModule } from 'iqb-components'; +import { AuthInterceptor } from './app.interceptor'; +import { WorkspaceModule } from './workspace'; + +@NgModule({ + declarations: [ + AppComponent, + StartComponent, + AboutComponent + ], + imports: [ + BrowserModule, + BrowserAnimationsModule, + FlexLayoutModule, + MatButtonModule, + MatFormFieldModule, + MatMenuModule, + MatToolbarModule, + MatIconModule, + MatInputModule, + FlexLayoutModule, + MatCheckboxModule, + MatTooltipModule, + MatDialogModule, + MatTabsModule, + ReactiveFormsModule, + HttpClientModule, + AppRoutingModule, + IqbComponentsModule, + MatCardModule, + MatProgressSpinnerModule, + FlexLayoutModule, + WorkspaceModule + ], + providers: [ + { + provide: LocationStrategy, + useClass: HashLocationStrategy + }, + BackendService, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptor, + multi: true + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/src/app/app.module.ts b/src/app/app.module.ts.TC similarity index 100% rename from src/app/app.module.ts rename to src/app/app.module.ts.TC diff --git a/src/app/backend.service.ts.ADMIN b/src/app/backend.service.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..8b23dd41e5fad8bebdc5756c0a5fde989fffff5d --- /dev/null +++ b/src/app/backend.service.ts.ADMIN @@ -0,0 +1,34 @@ +import { LoginData } from './app.interfaces'; +import { Injectable, Inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ErrorHandler, ServerError } from 'iqb-components'; + + +@Injectable() +export class BackendService { + + constructor( + @Inject('SERVER_URL') private serverUrl: string, + private http: HttpClient) { + + this.serverUrl = this.serverUrl + 'php/'; + } + + login(name: string, password: string): Observable<LoginData | ServerError> { + return this.http + .post<LoginData>(this.serverUrl + 'login.php/login', {n: name, p: password}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + getLoginData(adminToken: string): Observable<LoginData | ServerError> { + return this.http + .post<LoginData>(this.serverUrl + 'login.php/login', {at: adminToken}) + .pipe( + catchError(ErrorHandler.handle) + ); + } +} diff --git a/src/app/backend.service.ts b/src/app/backend.service.ts.TC similarity index 100% rename from src/app/backend.service.ts rename to src/app/backend.service.ts.TC diff --git a/src/app/iqb-files/index.ts b/src/app/iqb-files/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9095370f6a7600269b5a9319c12e14fb3df7392 --- /dev/null +++ b/src/app/iqb-files/index.ts @@ -0,0 +1,3 @@ +export { IqbFilesUploadQueueComponent } from './iqbFilesUploadQueue/iqbFilesUploadQueue.component'; +export { IqbFilesUploadInputForDirective } from './iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive'; +export { IqbFilesModule } from './iqb-files.module'; diff --git a/src/app/iqb-files/iqb-files.module.ts b/src/app/iqb-files/iqb-files.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..49b63fb587da935f2f22c2c03d8f1ecab090cb1a --- /dev/null +++ b/src/app/iqb-files/iqb-files.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { IqbFilesUploadComponent } from './iqbFilesUpload/iqbFilesUpload.component'; +import { IqbFilesUploadQueueComponent } from './iqbFilesUploadQueue/iqbFilesUploadQueue.component'; +import { IqbFilesUploadInputForDirective } from './iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive'; + +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatIconModule } from '@angular/material/icon'; +import { CommonModule } from '@angular/common'; +import { IqbComponentsModule } from 'iqb-components'; + + +@NgModule({ + imports: [ + MatButtonModule, + MatProgressBarModule, + MatIconModule, + MatCardModule, + IqbComponentsModule, + CommonModule + ], + declarations: [ + IqbFilesUploadComponent, + IqbFilesUploadQueueComponent, + IqbFilesUploadInputForDirective + ], + exports: [ + IqbFilesUploadQueueComponent, + IqbFilesUploadInputForDirective, + ] +}) +export class IqbFilesModule { } diff --git a/src/app/iqb-files/iqb-files.scss b/src/app/iqb-files/iqb-files.scss new file mode 100644 index 0000000000000000000000000000000000000000..a7b1fc4369a18093f0532b9713b9a937046f13ff --- /dev/null +++ b/src/app/iqb-files/iqb-files.scss @@ -0,0 +1,61 @@ + + +.dropzone { + background-color: brown; + width: 100px; + height: 100px; +} + + +mat-card { + padding: 15px; +} + +.example-section { + display: flex; + align-content: center; + align-items: center; + height: 10px; +} + +.file-info, .file-info-error { + font-size: .85rem; +} + +.file-info-error { + color: red; +} + +#drop_zone { + border: 5px solid blue; + width: 200px; + height: 100px; +} + +.action { + cursor: pointer; + outline: none; +} + +a.disabled { + pointer-events: none; +} + +.upload-drop-zone { + height: 200px; + border-width: 2px; + margin-bottom: 20px; +} + +/* skin.css Style*/ +.upload-drop-zone { + color: #ccc; + border-style: dashed; + border-color: #ccc; + line-height: 200px; + text-align: center +} +.upload-drop-zone.drop { + color: #222; + border-color: #222; +} diff --git a/src/app/iqb-files/iqbFilesUpload/iqbFilesUpload.component.html b/src/app/iqb-files/iqbFilesUpload/iqbFilesUpload.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4307132f325359e92d09cc835d9ed9ce0a1c4953 --- /dev/null +++ b/src/app/iqb-files/iqbFilesUpload/iqbFilesUpload.component.html @@ -0,0 +1,13 @@ +<mat-card> + <span class="file-info">{{file.name}} ({{file.size | bytes}})</span> + <section *ngIf="status<=1" class="example-section"> + <mat-progress-bar class="example-margin" [value]="progressPercentage"></mat-progress-bar> + <a *ngIf="status == 0"><mat-icon class="action" (click)="upload()">file_upload</mat-icon></a> + <mat-icon class="action" (click)="remove()">cancel</mat-icon> + </section> + <span *ngIf="status == 1" class="file-info">{{progressPercentage}} %</span><br/> + <span *ngIf="status != 1"> + <span *ngIf="status == 3" class="file-info-error">{{statustext}}</span> + <span *ngIf="status != 3" class="file-info">{{statustext}}</span> + </span> +</mat-card> diff --git a/src/app/iqb-files/iqbFilesUpload/iqbFilesUpload.component.ts b/src/app/iqb-files/iqbFilesUpload/iqbFilesUpload.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..bafd647b1b5c404deef2324a774a3c943cf1c8ad --- /dev/null +++ b/src/app/iqb-files/iqbFilesUpload/iqbFilesUpload.component.ts @@ -0,0 +1,190 @@ +import { Component, EventEmitter, Input, OnInit, Output, HostBinding } from '@angular/core'; +import { HttpClient, HttpEventType, HttpHeaders, HttpParams, + HttpErrorResponse, HttpEvent } from '@angular/common/http'; + + +@Component({ + selector: 'iqb-files-upload', + templateUrl: `./iqbFilesUpload.component.html`, + exportAs: 'iqbFilesUpload', + styleUrls: ['./../iqb-files.scss'], + }) + + export class IqbFilesUploadComponent implements OnInit { + @HostBinding('class') myclass = 'iqb-files-upload'; + + constructor( + private myHttpClient: HttpClient) { } + + // '''''''''''''''''''''''' + private _status: UploadStatus; + get status(): UploadStatus { + return this._status; + } + + set status(newstatus: UploadStatus) { + this._status = newstatus; + this.statusChangedEvent.emit(this); + } + + // '''''''''''''''''''''''' + private requestResponseText: string; + get statustext(): string { + let myreturn = ''; + switch (this._status) { + case UploadStatus.busy: { + myreturn = 'Bitte warten'; + break; + } + case UploadStatus.ready: { + myreturn = 'Bereit'; + break; + } + default: { + myreturn = this.requestResponseText; + break; + } + } + return myreturn; + } + + /* Http request input bindings */ + @Input() + httpUrl = 'http://localhost:8080'; + + @Input() + httpRequestHeaders: HttpHeaders | { + [header: string]: string | string[]; + } = new HttpHeaders().set('Content-Type', 'multipart/form-data'); + + @Input() + httpRequestParams: HttpParams | { + [param: string]: string | string[]; + } = new HttpParams(); + + @Input() + fileAlias = 'file'; + + @Input() + tokenName = ''; + + @Input() + token = ''; + + @Input() + folderName = ''; + + @Input() + folder = ''; + + @Input() + get file(): any { + return this._file; + } + set file(file: any) { + this._file = file; + this._filedate = this._file.lastModified; + this.total = this._file.size; + } + + @Input() + set id(id: number) { + this._id = id; + } + + get id(): number { + return this._id; + } + + @Output() removeFileRequestEvent = new EventEmitter<IqbFilesUploadComponent>(); + @Output() statusChangedEvent = new EventEmitter<IqbFilesUploadComponent>(); + + private progressPercentage = 0; + public loaded = 0; + private total = 0; + private _file: any; + private _filedate = ''; + private _id: number; + private fileUploadSubscription: any; + + + ngOnInit() { + this._status = UploadStatus.ready; + this.requestResponseText = ''; + this.upload(); + } + + // ================================================================== + private upload(): void { + if (this.status === UploadStatus.ready) { + + this.status = UploadStatus.busy; + const formData = new FormData(); + formData.set(this.fileAlias, this._file, this._file.name); + if ((typeof this.tokenName !== 'undefined') && (typeof this.token !== 'undefined')) { + if (this.tokenName.length > 0) { + formData.append(this.tokenName, this.token); + } + } + if ((typeof this.folderName !== 'undefined') && (typeof this.folder !== 'undefined')) { + if (this.folderName.length > 0) { + formData.append(this.folderName, this.folder); + } + } + this.fileUploadSubscription = this.myHttpClient.post(this.httpUrl, formData, { + // headers: this.httpRequestHeaders, + observe: 'events', + params: this.httpRequestParams, + reportProgress: true, + responseType: 'json' + }).subscribe((event: HttpEvent<any>) => { + if (event.type === HttpEventType.UploadProgress) { + this.progressPercentage = Math.floor( event.loaded * 100 / event.total ); + this.loaded = event.loaded; + this.total = event.total; + this.status = UploadStatus.busy; + } else if (event.type === HttpEventType.Response) { + this.requestResponseText = event.body; + if ((this.requestResponseText.length > 5) && (this.requestResponseText.substr(0, 2) === 'e:')) { + this.requestResponseText = this.requestResponseText.substr(2); + this.status = UploadStatus.error; + } else { + this.status = UploadStatus.ok; + this.remove(); + } + } + }, (errorObj: HttpErrorResponse) => { + if (this.fileUploadSubscription) { + this.fileUploadSubscription.unsubscribe(); + } + + this.status = UploadStatus.error; + if (errorObj.status === 401) { + this.requestResponseText = 'Fehler: Zugriff verweigert - bitte (neu) anmelden!'; + } else if (errorObj.status === 503) { + this.requestResponseText = 'Fehler: Server meldet Problem mit Datenbank oder Datei zu groß.'; + } else if (errorObj.error instanceof ErrorEvent) { + this.requestResponseText = 'Fehler: ' + (<ErrorEvent>errorObj.error).message; + } else { + this.requestResponseText = 'Fehler: ' + errorObj.message; + } + }); + } + } + + // ================================================================== + public remove(): void { + if (this.fileUploadSubscription) { + this.fileUploadSubscription.unsubscribe(); + } + this.removeFileRequestEvent.emit(this); + } + +} + +export enum UploadStatus { + ready, + busy, + ok, + error +} diff --git a/src/app/iqb-files/iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive.ts b/src/app/iqb-files/iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f2bc7d5b0e10acb6a1241bc01654155edacb1cf --- /dev/null +++ b/src/app/iqb-files/iqbFilesUploadInputFor/iqbFilesUploadInputFor.directive.ts @@ -0,0 +1,59 @@ +import { Component, Directive, ElementRef, EventEmitter, HostListener, + Input, OnDestroy, OnInit, Output } from '@angular/core'; + + + @Directive({ + selector: 'input[iqbFilesUploadInputFor], div[iqbFilesUploadInputFor]', + }) + export class IqbFilesUploadInputForDirective { + + + private _queue: any = null; + private _element: HTMLElement; + + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + constructor(private element: ElementRef) { + this._element = this.element.nativeElement; + } + + + @Input('iqbFilesUploadInputFor') + set filesUploadQueue(value: any) { + if (value) { + this._queue = value; + } + } + + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @HostListener('change') + public onChange(): any { + const files = this.element.nativeElement.files; + // this.onFileSelected.emit(files); + + for (let i = 0; i < files.length; i++) { + this._queue.add(files[i]); + } + this.element.nativeElement.value = ''; + } + + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @HostListener('drop', [ '$event' ]) + public onDrop(event: any): any { + const files = event.dataTransfer.files; + // this.onFileSelected.emit(files); + + for (let i = 0; i < files.length; i++) { + this._queue.add(files[i]); + } + event.preventDefault(); + event.stopPropagation(); + this.element.nativeElement.value = ''; + } + + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @HostListener('dragover', [ '$event' ]) + public onDropOver(event: any): any { + event.preventDefault(); + } + + } diff --git a/src/app/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.html b/src/app/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e40725e94508c59fb46ba55841108853e8ecca03 --- /dev/null +++ b/src/app/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.html @@ -0,0 +1,17 @@ +<iqb-files-upload + [file]="file" + [id]="i" + *ngFor="let file of files; let i = index" + [httpUrl]="httpUrl" + [fileAlias]="fileAlias" + [tokenName]="tokenName" + [token]="token" + [folderName]="folderName" + [folder]="folder" + + (removeFileRequestEvent)="removeFile($event)" + (statusChangedEvent)="analyseStatus()"> +</iqb-files-upload> +<br/> +<button mat-raised-button color="primary" *ngIf="files.length > 0" [disabled]="disableClearButton" + (click)="removeAll()">OK</button> diff --git a/src/app/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts b/src/app/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9bd7df6004e7c96e94dd6d21619f6d9e0f160c9e --- /dev/null +++ b/src/app/iqb-files/iqbFilesUploadQueue/iqbFilesUploadQueue.component.ts @@ -0,0 +1,108 @@ +import { Component, EventEmitter, OnInit, OnDestroy, QueryList, ViewChildren, Input, Output } from '@angular/core'; +import { IqbFilesUploadComponent, UploadStatus } from '../iqbFilesUpload/iqbFilesUpload.component'; +import { HttpHeaders, HttpParams } from '@angular/common/http'; + + +/** + * A material design file upload queue component. + */ +@Component({ + selector: 'iqb-files-upload-queue', + templateUrl: `iqbFilesUploadQueue.component.html`, + exportAs: 'iqbFilesUploadQueue', + }) + export class IqbFilesUploadQueueComponent implements OnDestroy { + + @ViewChildren(IqbFilesUploadComponent) fileUploads: QueryList<IqbFilesUploadComponent>; + + public files: Array<any> = []; + private numberOfErrors = 0; + private numberOfUploads = 0; + private disableClearButton = true; + + /* Http request input bindings */ + @Input() + httpUrl: string; + + @Input() + httpRequestHeaders: HttpHeaders | { + [header: string]: string | string[]; + } = new HttpHeaders().set('Content-Type', 'multipart/form-data'); + + @Input() + httpRequestParams: HttpParams | { + [param: string]: string | string[]; + } = new HttpParams(); + + @Input() + fileAlias: string; + + @Input() + tokenName: string; + + @Input() + token: string; + + @Input() + folderName: string; + + @Input() + folder: string; + + @Output() uploadCompleteEvent = new EventEmitter<IqbFilesUploadQueueComponent>(); + + // +++++++++++++++++++++++++++++++++++++++++++++++++ + add(file: any) { + this.files.push(file); + } + + // +++++++++++++++++++++++++++++++++++++++++++++++++ + public removeAll() { + this.files.splice(0, this.files.length); + } + + // +++++++++++++++++++++++++++++++++++++++++++++++++ + ngOnDestroy() { + if (this.files) { + this.removeAll(); + } + } + + // +++++++++++++++++++++++++++++++++++++++++++++++++ + removeFile(fileToRemove: IqbFilesUploadComponent) { + this.files.splice(fileToRemove.id, 1); + } + +/* // +++++++++++++++++++++++++++++++++++++++++++++++++ + updateStatus() { + this.numberOfErrors = 0; + this.numberOfUploads = 0; + + this.fileUploads.forEach((fileUpload) => { + + fileUpload.upload(); + }); + } */ + + // +++++++++++++++++++++++++++++++++++++++++++++++++ + analyseStatus() { + let someoneiscomplete = false; + let someoneisbusy = false; + let someoneisready = false; + this.fileUploads.forEach((fileUpload) => { + if ((fileUpload.status === UploadStatus.ok) || (fileUpload.status === UploadStatus.error)) { + someoneiscomplete = true; + } else if (fileUpload.status === UploadStatus.busy) { + someoneisbusy = true; + return; // forEach + } else if (fileUpload.status === UploadStatus.ready) { + someoneisready = true; + } + }); + + if (someoneiscomplete && !someoneisbusy) { + this.uploadCompleteEvent.emit(); + this.disableClearButton = false; + } + } +} diff --git a/src/app/maindata.service.spec.ts.ADMIN b/src/app/maindata.service.spec.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..16e5e0e6d4e1d7660637c67c77a2cb8ff736d88c --- /dev/null +++ b/src/app/maindata.service.spec.ts.ADMIN @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { MainDataService } from './maindata.service'; + +describe('MaindataService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: MainDataService = TestBed.get(MainDataService); + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/maindata.service.spec.ts b/src/app/maindata.service.spec.ts.TC similarity index 100% rename from src/app/maindata.service.spec.ts rename to src/app/maindata.service.spec.ts.TC diff --git a/src/app/maindata.service.ts.ADMIN b/src/app/maindata.service.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..7b011a327efb27a446991e175b28348077d56d9a --- /dev/null +++ b/src/app/maindata.service.ts.ADMIN @@ -0,0 +1,88 @@ +import { BehaviorSubject } from 'rxjs'; +import { LoginData } from './app.interfaces'; +import { Injectable } from '@angular/core'; +import { ServerError } from 'iqb-components'; + +@Injectable({ + providedIn: 'root' +}) +export class MainDataService { + private static defaultLoginData: LoginData = { + admintoken: '', + name: '', + workspaces: [], + is_superadmin: false + }; + + public get adminToken(): string { + const myLoginData = this.loginData$.getValue(); + if (myLoginData) { + return myLoginData.admintoken; + } else { + return ''; + } + } + + + public loginData$ = new BehaviorSubject<LoginData>(MainDataService.defaultLoginData); + public globalErrorMsg$ = new BehaviorSubject<ServerError>(null); + + + setNewLoginData(logindata?: LoginData) { + const myLoginData: LoginData = { + admintoken: MainDataService.defaultLoginData.admintoken, + name: MainDataService.defaultLoginData.name, + workspaces: MainDataService.defaultLoginData.workspaces, + is_superadmin: MainDataService.defaultLoginData.is_superadmin + }; + + if (logindata) { + if ( + (logindata.admintoken.length > 0) && + (logindata.name.length > 0)) { + myLoginData.admintoken = logindata.admintoken; + myLoginData.name = logindata.name; + myLoginData.workspaces = logindata.workspaces; + myLoginData.is_superadmin = logindata.is_superadmin; + } + } + this.loginData$.next(myLoginData); + localStorage.setItem('at', myLoginData.admintoken); + } + + setNewErrorMsg(err: ServerError = null) { + this.globalErrorMsg$.next(err); + } + + getWorkspaceName(ws: number): string { + let myreturn = ''; + if (ws > 0) { + const myLoginData = this.loginData$.getValue(); + if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) { + for (let i = 0; i < myLoginData.workspaces.length; i++) { + if (myLoginData.workspaces[i].id == ws) { + myreturn = myLoginData.workspaces[i].name; + break; + } + } + } + } + return myreturn; + } + + getWorkspaceRole(ws: number): string { + let myreturn = ''; + if (ws > 0) { + const myLoginData = this.loginData$.getValue(); + if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) { + for (let i = 0; i < myLoginData.workspaces.length; i++) { + if (myLoginData.workspaces[i].id == ws) { + myreturn = myLoginData.workspaces[i].role; + break; + } + } + } + } + return myreturn; + } +} diff --git a/src/app/maindata.service.ts b/src/app/maindata.service.ts.TC similarity index 100% rename from src/app/maindata.service.ts rename to src/app/maindata.service.ts.TC diff --git a/src/app/start/start.component.css.ADMIN b/src/app/start/start.component.css.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..5b0d7dccc5371ab0350d50bf7bfddadcc41c2715 --- /dev/null +++ b/src/app/start/start.component.css.ADMIN @@ -0,0 +1,7 @@ +.mat-card { + margin: 10px; +} + +.status { + background-color: lightgrey; +} diff --git a/src/app/start/start.component.css b/src/app/start/start.component.css.TC similarity index 100% rename from src/app/start/start.component.css rename to src/app/start/start.component.css.TC diff --git a/src/app/start/start.component.html.ADMIN b/src/app/start/start.component.html.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..269a124b4213bfc3bc0761e9bc480186e6769e88 --- /dev/null +++ b/src/app/start/start.component.html.ADMIN @@ -0,0 +1,74 @@ +<div class="logo"> + <a [routerLink]="['/']"> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + </a> +</div> +<div class="page-body"> + <div fxLayout="row wrap" fxLayoutAlign="center stretch" style="padding: 30px;"> + + + <!-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --> + <mat-card fxFlex="0 0 400px" fxLayout="column" *ngIf="showLogin"> + <!-- - - - - - - - - - - - - - - - - --> + <form [formGroup]="adminloginform" (ngSubmit)="login()"> + <mat-card-title>Anmelden</mat-card-title> + <mat-card-content fxLayout="column"> + <mat-form-field> + <input matInput formControlName="testname" placeholder="Anmeldename" (keyup.enter)="pw.focus()"> + </mat-form-field> + <mat-form-field> + <input matInput #pw type="password" formControlName="testpw" placeholder="Kennwort" (keyup.enter)="login()"> + </mat-form-field> + </mat-card-content> + <mat-card-actions> + <button mat-raised-button type="submit" [disabled]="adminloginform.invalid" color="primary">Weiter</button> + </mat-card-actions> + </form> + <p class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</p> + </mat-card> + + <mat-card fxFlex="0 0 400px" fxLayout="column" *ngIf="!showLogin"> + <mat-card-title>Studie wählen</mat-card-title> + <mat-card-content> + <div fxLayout="row" fxLayoutGap="10px" fxLayout="column"> + <p *ngIf="(mds.loginData$ | async)?.workspaces.length === 0"> + Für diese Anmeldung wurden keine Studien gefunden. + </p> + <button mat-raised-button color="primary" (click)="buttonGotoWorkspace(ws)" + *ngFor="let ws of (mds.loginData$ | async)?.workspaces"> + {{ws.name}} + </button> + </div> + </mat-card-content> + <mat-card-actions> + <button mat-raised-button color="foreground" *ngIf="(mds.loginData$ | async)?.is_superadmin" [routerLink]="['/superadmin']">Nutzer/Arbeitsbereiche</button> + <button mat-raised-button color="foreground" (click)="mds.setNewLoginData()">Anmeldung ändern</button> + </mat-card-actions> + </mat-card> + + <!-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --> + <mat-card fxFlex="0 2 400px" fxLayout="column" class="status"> + <mat-card-title>IQB-Testcenter Verwaltung</mat-card-title> + + <!-- - - - - - - - - - - - - - - - - --> + <mat-card-content> + <div> + <p>Das <a href="http://www.iqb.hu-berlin.de" target="_blank">Institut zur Qualitätsentwicklung im Bildungswesen</a> + betreibt auf diesen Seiten eine Pilotanwendung für das computerbasierte Leistungstesten von + Schülerinnen und Schülern. Dies ist die Web-Anwendung zur Verwaltung der Testinhalte und -ergebnisse. + Der Zugang ist nur möglich, wenn Sie vom IQB + Zugangsdaten erhalten haben. Es sind keine weiteren Seiten öffentlich verfügbar.</p> + </div> + <div *ngIf="!showLogin"> + <ul> + <li>angemeldet als: {{ (mds.loginData$ | async)?.name }}</li> + <li *ngIf="(mds.loginData$ | async)?.is_superadmin">zum Ändern von Nutzerrechten und Arbeitsbereichen berechtigt</li> + </ul> + </div> + </mat-card-content> + <mat-card-actions> + <button mat-raised-button color="foreground" [routerLink]="['/about']">Impressum/Datenschutz</button> + </mat-card-actions> + </mat-card> + </div> +</div> diff --git a/src/app/start/start.component.html b/src/app/start/start.component.html.TC similarity index 100% rename from src/app/start/start.component.html rename to src/app/start/start.component.html.TC diff --git a/src/app/start/start.component.spec.ts.ADMIN b/src/app/start/start.component.spec.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..10f012b9c1cdefa4501e30a907b3d4dbb54f24cc --- /dev/null +++ b/src/app/start/start.component.spec.ts.ADMIN @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StartComponent } from './start.component'; + +describe('HomeComponent', () => { + let component: StartComponent; + let fixture: ComponentFixture<StartComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StartComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/start/start.component.spec.ts b/src/app/start/start.component.spec.ts.TC similarity index 100% rename from src/app/start/start.component.spec.ts rename to src/app/start/start.component.spec.ts.TC diff --git a/src/app/start/start.component.ts.ADMIN b/src/app/start/start.component.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..275c0388fd3bcc77dca2734bb4e9f34d7e9c92d9 --- /dev/null +++ b/src/app/start/start.component.ts.ADMIN @@ -0,0 +1,64 @@ +import { LoginData, WorkspaceData } from '../app.interfaces'; +import { BackendService } from '../backend.service'; +import { MainDataService } from '../maindata.service'; +import { Router } from '@angular/router'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { ServerError } from 'iqb-components'; + + +@Component({ + templateUrl: './start.component.html', + styleUrls: ['./start.component.css'] +}) +export class StartComponent implements OnInit, OnDestroy { + adminloginform: FormGroup; + private loginDataSubscription: Subscription = null; + public showLogin = true; + + constructor(private fb: FormBuilder, + private mds: MainDataService, + private bs: BackendService, + private router: Router) { } + + ngOnInit() { + this.adminloginform = this.fb.group({ + testname: this.fb.control('', [Validators.required, Validators.minLength(3)]), + testpw: this.fb.control('', [Validators.required, Validators.minLength(3)]) + }); + this.loginDataSubscription = this.mds.loginData$.subscribe(logindata => { + this.showLogin = logindata.admintoken.length === 0; + }); + } + + login() { + if (this.adminloginform.valid) { + this.bs.login( + this.adminloginform.get('testname').value, this.adminloginform.get('testpw').value + ).subscribe(admindata => { + if (admindata instanceof ServerError) { + this.mds.setNewLoginData(); + this.mds.setNewErrorMsg(admindata as ServerError); + } else { + this.mds.setNewLoginData(admindata as LoginData); + this.mds.setNewErrorMsg(); + } + }); + } + } + + buttonGotoWorkspace(ws: WorkspaceData) { + if (ws.role === 'MO') { + this.router.navigateByUrl('/ws/' + ws.id.toString() + '/monitor'); + } else { + this.router.navigateByUrl('/ws/' + ws.id.toString() + '/files'); + } + } + + ngOnDestroy() { + if (this.loginDataSubscription !== null) { + this.loginDataSubscription.unsubscribe(); + } + } +} diff --git a/src/app/start/start.component.ts b/src/app/start/start.component.ts.TC similarity index 100% rename from src/app/start/start.component.ts rename to src/app/start/start.component.ts.TC diff --git a/src/app/superadmin/backend.service.spec.ts b/src/app/superadmin/backend.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c31039d7e5bcc7bc70e7213ce06744a0dc6ed009 --- /dev/null +++ b/src/app/superadmin/backend.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { BackendService } from './backend.service'; + +describe('BackendService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [BackendService] + }); + }); + + it('should be created', inject([BackendService], (service: BackendService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/superadmin/backend.service.ts b/src/app/superadmin/backend.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..750e14935b874b4765d939237f60c475746281ea --- /dev/null +++ b/src/app/superadmin/backend.service.ts @@ -0,0 +1,134 @@ +import { Injectable, Inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + + +@Injectable() + +export class BackendService { + + constructor( + @Inject('SERVER_URL') private readonly serverUrl: string, + private http: HttpClient) { + this.serverUrl = this.serverUrl + 'php/sys.php/'; + } + + getUsers(): Observable<NameOnly[]> { + return this.http + .get<NameOnly[]>(this.serverUrl + 'users') + .pipe( + catchError(() => []) + ); + } + + addUser(name: string, password: string): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'user/add', {n: name, p: password}) + .pipe( + catchError(() => of(false)) + ); + } + + changePassword(name: string, password: string): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'user/pw', {n: name, p: password}) + .pipe( + catchError(() => of(false)) + ); + } + + deleteUsers(users: string[]): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'users/delete', {u: users}) + .pipe( + catchError(() => of(false)) + ); + } + + getWorkspacesByUser(username: string): Observable<IdRoleData[]> { + return this.http + .get<IdLabelSelectedData[]>(this.serverUrl + 'workspaces?u=' + username) + .pipe( + catchError(() => []) + ); + } + + setWorkspacesByUser(user: string, accessTo: IdRoleData[]): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'user/workspaces', {u: user, ws: accessTo}) + .pipe( + catchError(() => of(false)) + ); + } + + addWorkspace(name: string): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'workspace/add', {n: name}) + .pipe( + catchError(() => of(false)) + ); + } + + renameWorkspace(wsId: number, wsName: string): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'workspace/rename', {ws: wsId, n: wsName}) + .pipe( + catchError(() => of(false)) + ); + } + + deleteWorkspaces(workspaces: number[]): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'workspaces/delete', {ws: workspaces}) + .pipe( + catchError(() => of(false)) + ); + } + + getUsersByWorkspace(workspaceId: number): Observable<IdRoleData[]> { + return this.http + .get<IdRoleData[]>(this.serverUrl + 'users?ws=' + workspaceId) + .pipe( + catchError(() => []) + ); + } + + setUsersByWorkspace(workspace: number, accessing: IdRoleData[]): Observable<Boolean> { + return this.http + .post<Boolean>(this.serverUrl + 'workspace/users', {ws: workspace, u: accessing}) + .pipe( + catchError(() => of(false)) + ); + } + + getWorkspaces(): Observable<IdAndName[]> { + return this.http + .get<IdAndName[]>(this.serverUrl + 'workspaces') + .pipe( + catchError(() => []) + ); + } +} + + +export interface NameOnly { + name: string; +} + +export interface IdAndName { + id: number; + name: string; +} + +export interface IdLabelSelectedData { + id: number; + label: string; + selected: boolean; +} + +export interface IdRoleData { + id: number; + label: string; + role: string; +} diff --git a/src/app/superadmin/index.ts b/src/app/superadmin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..57d8e210df0fc7fe9ce1268af661b2209d740bcb --- /dev/null +++ b/src/app/superadmin/index.ts @@ -0,0 +1,2 @@ +export { SuperadminComponent } from './superadmin.component'; +export { SuperadminModule } from './superadmin.module'; diff --git a/src/app/superadmin/superadmin-routing.module.ts.ADMIN b/src/app/superadmin/superadmin-routing.module.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..edbabde86edf28d79a64465db97d6530510f884c --- /dev/null +++ b/src/app/superadmin/superadmin-routing.module.ts.ADMIN @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { WorkspacesComponent } from './workspaces/workspaces.component'; +import { UsersComponent } from './users/users.component'; +import { SuperadminComponent } from './superadmin.component'; + + +const routes: Routes = [ + { + path: '', + component: SuperadminComponent, + children: [ + {path: '', redirectTo: 'users', pathMatch: 'full'}, + {path: 'users', component: UsersComponent}, + {path: 'workspaces', component: WorkspacesComponent}, + {path: '**', component: UsersComponent} + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class SuperadminRoutingModule { } diff --git a/src/app/superadmin/superadmin-routing.module.ts b/src/app/superadmin/superadmin-routing.module.ts.TC similarity index 100% rename from src/app/superadmin/superadmin-routing.module.ts rename to src/app/superadmin/superadmin-routing.module.ts.TC diff --git a/src/app/superadmin/superadmin.component.css.ADMIN b/src/app/superadmin/superadmin.component.css.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..90fca5595686bed01fc1036a2b0dd8ca7bb1d92c --- /dev/null +++ b/src/app/superadmin/superadmin.component.css.ADMIN @@ -0,0 +1,18 @@ +.adminbackground { + flex: 10 0 900px; + box-shadow: 5px 10px 20px black; + background-color: white; + min-height: 85%; + margin: 15px; + padding: 25px; +} + +#buttonsContainer { + color: rgb(253, 249, 196); + padding: 0 10px 0 0; + font-weight: bold; +} + +#buttonsContainer img { + width: 100px; +} diff --git a/src/app/superadmin/superadmin.component.css b/src/app/superadmin/superadmin.component.css.TC similarity index 100% rename from src/app/superadmin/superadmin.component.css rename to src/app/superadmin/superadmin.component.css.TC diff --git a/src/app/superadmin/superadmin.component.html.ADMIN b/src/app/superadmin/superadmin.component.html.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..4f4fc3253f01570b92277d0b3bfc41964243889f --- /dev/null +++ b/src/app/superadmin/superadmin.component.html.ADMIN @@ -0,0 +1,25 @@ +<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="start center"> + <a [routerLink]="['/']"> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + </a> + <div fxLayout="row wrap" fxLayoutAlign="space-between center" fxFlex> + <div class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</div> + <div>IQB-Testcenter Systemverwaltung</div> + </div> +</div> +<div class="page-body"> + <div class="adminbackground"> + + <nav mat-tab-nav-bar> + <a mat-tab-link + *ngFor="let link of navLinks" + [routerLink]="link.path" + routerLinkActive #rla="routerLinkActive" + [active]="rla.isActive"> + {{link.label}} + </a> + </nav> + + <router-outlet></router-outlet> + </div> +</div> diff --git a/src/app/superadmin/superadmin.component.html b/src/app/superadmin/superadmin.component.html.TC similarity index 100% rename from src/app/superadmin/superadmin.component.html rename to src/app/superadmin/superadmin.component.html.TC diff --git a/src/app/superadmin/superadmin.component.ts.ADMIN b/src/app/superadmin/superadmin.component.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..e06d11db00d1ad715dc76d3224db51307633552d --- /dev/null +++ b/src/app/superadmin/superadmin.component.ts.ADMIN @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core'; +import { MainDataService } from '../maindata.service'; + + + +@Component({ + templateUrl: './superadmin.component.html', + styleUrls: ['./superadmin.component.css'] +}) +export class SuperadminComponent { + constructor( + public mds: MainDataService + ) { } + + public navLinks = [ + {path: 'users', label: 'Users'}, + {path: 'workspaces', label: 'Arbeitsbereiche'} + ]; +} diff --git a/src/app/superadmin/superadmin.component.ts b/src/app/superadmin/superadmin.component.ts.TC similarity index 100% rename from src/app/superadmin/superadmin.component.ts rename to src/app/superadmin/superadmin.component.ts.TC diff --git a/src/app/superadmin/superadmin.module.spec.ts b/src/app/superadmin/superadmin.module.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8404f336e02b67d87ec1acf4e839bc61b2deb80 --- /dev/null +++ b/src/app/superadmin/superadmin.module.spec.ts @@ -0,0 +1,13 @@ +import { SuperadminModule } from './superadmin.module'; + +describe('SuperadminModule', () => { + let superadminModule: SuperadminModule; + + beforeEach(() => { + superadminModule = new SuperadminModule(); + }); + + it('should create an instance', () => { + expect(superadminModule).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/superadmin.module.ts.ADMIN b/src/app/superadmin/superadmin.module.ts.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..de70a8955d8ad136a5ace9689094e87a43e48cf1 --- /dev/null +++ b/src/app/superadmin/superadmin.module.ts.ADMIN @@ -0,0 +1,78 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { SuperadminRoutingModule } from './superadmin-routing.module'; +import { WorkspacesComponent } from './workspaces/workspaces.component'; +import { UsersComponent } from './users/users.component'; +import { SuperadminComponent } from './superadmin.component'; +import { BackendService } from './backend.service'; +import { IqbFilesModule } from '../iqb-files'; +import { NewuserComponent } from './users/newuser/newuser.component'; +import { NewpasswordComponent } from './users/newpassword/newpassword.component'; +import { NewworkspaceComponent } from './workspaces/newworkspace/newworkspace.component'; +import { EditworkspaceComponent } from './workspaces/editworkspace/editworkspace.component'; +import { IqbComponentsModule } from 'iqb-components'; + + +@NgModule({ + imports: [ + CommonModule, + SuperadminRoutingModule, + IqbFilesModule, + IqbComponentsModule, + MatTableModule, + MatTabsModule, + MatIconModule, + MatSelectModule, + MatCheckboxModule, + MatSortModule, + ReactiveFormsModule, + MatProgressSpinnerModule, + MatDialogModule, + MatButtonModule, + MatTooltipModule, + MatFormFieldModule, + MatInputModule, + MatToolbarModule, + MatSnackBarModule, + FlexLayoutModule + ], + exports: [ + SuperadminComponent, + ], + declarations: [ + WorkspacesComponent, + UsersComponent, + SuperadminComponent, + NewuserComponent, + NewpasswordComponent, + NewworkspaceComponent, + EditworkspaceComponent + ], + providers: [ + BackendService, + ], + entryComponents: [ + NewuserComponent, + NewpasswordComponent, + NewworkspaceComponent, + EditworkspaceComponent + ] +}) +export class SuperadminModule { } diff --git a/src/app/superadmin/superadmin.module.ts b/src/app/superadmin/superadmin.module.ts.TC similarity index 100% rename from src/app/superadmin/superadmin.module.ts rename to src/app/superadmin/superadmin.module.ts.TC diff --git a/src/app/superadmin/users/newpassword/newpassword.component.css b/src/app/superadmin/users/newpassword/newpassword.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/superadmin/users/newpassword/newpassword.component.html b/src/app/superadmin/users/newpassword/newpassword.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8685c172a39662b40f6a6734944552a13b17d09f --- /dev/null +++ b/src/app/superadmin/users/newpassword/newpassword.component.html @@ -0,0 +1,20 @@ +<form [formGroup]="newpasswordform"> + <h1 mat-dialog-title>Kennwort ändern</h1> + + <mat-dialog-content> + <div class="infobox"> + <p>Ändern des Kennwortes für Nutzer/in "{{ data.name }}".</p> + </div> + <p> + <mat-form-field class="full-width"> + <input matInput type="password" formControlName="pw" placeholder="Kennwort"> + </mat-form-field> + </p> + </mat-dialog-content> + + <mat-dialog-actions> + <button mat-raised-button color="primary" type="submit" [mat-dialog-close]="newpasswordform" [disabled]="newpasswordform.invalid">Speichern</button> + <button mat-raised-button [mat-dialog-close]="false">Abbrechen</button> + </mat-dialog-actions> + + </form> diff --git a/src/app/superadmin/users/newpassword/newpassword.component.spec.ts b/src/app/superadmin/users/newpassword/newpassword.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e945c2cdf5d97e56a0ba76bf977b2020c428596 --- /dev/null +++ b/src/app/superadmin/users/newpassword/newpassword.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewpasswordComponent } from './newpassword.component'; + +describe('NewpasswordComponent', () => { + let component: NewpasswordComponent; + let fixture: ComponentFixture<NewpasswordComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NewpasswordComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NewpasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/users/newpassword/newpassword.component.ts b/src/app/superadmin/users/newpassword/newpassword.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a001265d18cae4a65cf5de623c1ebfddff7f91d --- /dev/null +++ b/src/app/superadmin/users/newpassword/newpassword.component.ts @@ -0,0 +1,21 @@ +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Component, OnInit, Inject } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + templateUrl: './newpassword.component.html', + styleUrls: ['./newpassword.component.css'] +}) + +export class NewpasswordComponent implements OnInit { + newpasswordform: FormGroup; + + constructor(private fb: FormBuilder, + @Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit() { + this.newpasswordform = this.fb.group({ + pw: this.fb.control('', [Validators.required, Validators.minLength(3)]) + }); + } +} diff --git a/src/app/superadmin/users/newuser/newuser.component.css b/src/app/superadmin/users/newuser/newuser.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/superadmin/users/newuser/newuser.component.html b/src/app/superadmin/users/newuser/newuser.component.html new file mode 100644 index 0000000000000000000000000000000000000000..4a4d5c11f20c5873192f60bf454c5193b1785ece --- /dev/null +++ b/src/app/superadmin/users/newuser/newuser.component.html @@ -0,0 +1,26 @@ +<form [formGroup]="newuserform"> + <h1 mat-dialog-title>Neue/r Nutzer/in</h1> + + <mat-dialog-content> + <p> + <mat-form-field class="full-width"> + <input matInput formControlName="name" placeholder="Name" [value]="data.name"> + </mat-form-field> + </p> + <p> + <mat-form-field class="full-width"> + <input matInput type="password" formControlName="pw" placeholder="Kennwort"> + </mat-form-field> + </p> + <div class="infobox"> + <p>Nach dem Anlegen des Nutzers können Sie die Rechte zuweisen. + </p> + </div> + </mat-dialog-content> + + <mat-dialog-actions> + <button mat-raised-button color="primary" type="submit" [mat-dialog-close]="newuserform" [disabled]="newuserform.invalid">Speichern</button> + <button mat-raised-button [mat-dialog-close]="false">Abbrechen</button> + </mat-dialog-actions> + +</form> diff --git a/src/app/superadmin/users/newuser/newuser.component.spec.ts b/src/app/superadmin/users/newuser/newuser.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd37aa1b5db9f9dfc0a22a681ecb46ab7490302a --- /dev/null +++ b/src/app/superadmin/users/newuser/newuser.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewuserComponent } from './newuser.component'; + +describe('NewuserComponent', () => { + let component: NewuserComponent; + let fixture: ComponentFixture<NewuserComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NewuserComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NewuserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/users/newuser/newuser.component.ts b/src/app/superadmin/users/newuser/newuser.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..75ea8e3bfb23faf5dd77b480a98af7dde78c8b7a --- /dev/null +++ b/src/app/superadmin/users/newuser/newuser.component.ts @@ -0,0 +1,21 @@ +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Component, OnInit, Inject } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + templateUrl: './newuser.component.html', + styleUrls: ['./newuser.component.css'] +}) +export class NewuserComponent implements OnInit { + newuserform: FormGroup; + + constructor(private fb: FormBuilder, + @Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit() { + this.newuserform = this.fb.group({ + name: this.fb.control('', [Validators.required, Validators.minLength(3)]), + pw: this.fb.control('', [Validators.required, Validators.minLength(3)]) + }); + } +} diff --git a/src/app/superadmin/users/users.component.css b/src/app/superadmin/users/users.component.css new file mode 100644 index 0000000000000000000000000000000000000000..4c24b03c812046310b0988dea942ef0cdb956850 --- /dev/null +++ b/src/app/superadmin/users/users.component.css @@ -0,0 +1,8 @@ +.mat-raised-button { + min-width: 100px; + margin: 2px; +} + +.mat-checkbox { + margin: 0 3px; +} \ No newline at end of file diff --git a/src/app/superadmin/users/users.component.html b/src/app/superadmin/users/users.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e20f28032a86975a30ffc0b2a43dd28f8d1ab494 --- /dev/null +++ b/src/app/superadmin/users/users.component.html @@ -0,0 +1,85 @@ +<div class="columnhost" fxLayout="row" fxLayoutAlign="space-between start"> + <div class="spinner-container" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + + <!-- ============================================= --> + <div class="objectlist" fxLayout="column" fxFlex="50"> + <div fxLayout="row"> + <button mat-raised-button (click)="addObject()" matTooltip="Nutzer hinzufügen" matTooltipPosition="above"> + <mat-icon>add</mat-icon> + </button> + <button mat-raised-button (click)="deleteObject()" matTooltip="Markierte Nutzer löschen" matTooltipPosition="above"> + <mat-icon>delete</mat-icon> + </button> + <button mat-raised-button (click)="changePassword()" matTooltip="Kennwort ändern" matTooltipPosition="above"> + <mat-icon>edit</mat-icon> + </button> + </div> + + <mat-table *ngIf="isSuperadmin" [dataSource]="objectsDatasource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef fxFlex="70px"> + <mat-checkbox (change)="$event ? masterToggle() : null" + [checked]="tableselectionCheckbox.hasValue() && isAllSelected()" + [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()"> + </mat-checkbox> + </mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="70px"> + <mat-checkbox (click)="$event.stopPropagation()" + (change)="$event ? tableselectionCheckbox.toggle(row) : null" + [checked]="tableselectionCheckbox.isSelected(row)"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="name"> + <mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell> + <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;" (click)="selectRow(row)" + [style.background]="tableselectionRow.isSelected(row) ? 'lightblue' : ''"></mat-row> + </mat-table> + </div> + + <!-- ============================================= --> + <div *ngIf="isSuperadmin" fxLayout="column" fxFlex="40"> + + <div *ngIf="selectedUser.length == 0"> + <div>Zugriffsrechte für Arbeitsbereich(e):</div> + <div>Bitte links einen Nutzer wählen</div> + </div> + + <div *ngIf="selectedUser.length > 0" fxLayout="row" fxLayoutAlign="space-between center"> + <div>Zugriffsrechte für {{ selectedUser }}:</div> + <button mat-raised-button (click)="saveWorkspaces()" matTooltip="Speichern" + matTooltipPosition="above" [disabled]="!pendingWorkspaceChanges"> + <mat-icon>save</mat-icon> + </button> + </div> + + <mat-table [dataSource]="WorkspacelistDatasource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef mat-sort-header>RO | RW | MO</mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="100px"> + <mat-checkbox (change)="selectWorkspace(row, 'RO')" [checked]="row.role === 'RO'" matTooltip="RO"> + </mat-checkbox> + <mat-checkbox (change)="selectWorkspace(row, 'RW')" [checked]="row.role === 'RW'" matTooltip="RW"> + </mat-checkbox> + <mat-checkbox (change)="selectWorkspace(row, 'MO')" [checked]="row.role === 'MO'" matTooltip="MO"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="label"> + <mat-header-cell *matHeaderCellDef mat-sort-header> Arbeitsbereich </mat-header-cell> + <mat-cell *matCellDef="let row"> {{row.name}} </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedWorkspaceColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedWorkspaceColumns;"></mat-row> + </mat-table> + </div> +</div> diff --git a/src/app/superadmin/users/users.component.spec.ts b/src/app/superadmin/users/users.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..909b5bafc36b01915898fbd0fe8b0d5568aba476 --- /dev/null +++ b/src/app/superadmin/users/users.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UsersComponent } from './users.component'; + +describe('UsersComponent', () => { + let component: UsersComponent; + let fixture: ComponentFixture<UsersComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UsersComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UsersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/users/users.component.ts b/src/app/superadmin/users/users.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..62dde2f2bde27fa094dc7499750094626353c974 --- /dev/null +++ b/src/app/superadmin/users/users.component.ts @@ -0,0 +1,268 @@ +import { NewpasswordComponent } from './newpassword/newpassword.component'; +import { NewuserComponent } from './newuser/newuser.component'; +import { BackendService, NameOnly, IdRoleData } from '../backend.service'; +import { MatTableDataSource } from '@angular/material/table'; +import { ViewChild, OnDestroy } from '@angular/core'; + +import { Component, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatSort } from '@angular/material/sort'; +import { FormGroup } from '@angular/forms'; +import { SelectionModel } from '@angular/cdk/collections'; +import { + ConfirmDialogComponent, ConfirmDialogData, MessageDialogComponent, + MessageDialogData, MessageType +} from 'iqb-components'; +import { Subscription } from 'rxjs'; +import { MainDataService } from 'src/app/maindata.service'; + + +@Component({ + templateUrl: './users.component.html', + styleUrls: ['./users.component.css'] +}) +export class UsersComponent implements OnInit, OnDestroy { + public isSuperadmin = false; + public dataLoading = false; + public objectsDatasource: MatTableDataSource<NameOnly>; + public displayedColumns = ['selectCheckbox', 'name']; + private tableselectionCheckbox = new SelectionModel <NameOnly>(true, []); + private tableselectionRow = new SelectionModel <NameOnly>(false, []); + private selectedUser = ''; + + private pendingWorkspaceChanges = false; + public WorkspacelistDatasource: MatTableDataSource<IdRoleData>; + public displayedWorkspaceColumns = ['selectCheckbox', 'label']; + private logindataSubscription: Subscription = null; + + @ViewChild(MatSort, { static: false }) sort: MatSort; + + constructor( + private bs: BackendService, + private mds: MainDataService, + private newuserDialog: MatDialog, + private newpasswordDialog: MatDialog, + private deleteConfirmDialog: MatDialog, + private messsageDialog: MatDialog, + private snackBar: MatSnackBar + ) { + this.tableselectionRow.changed.subscribe( + r => { + if (r.added.length > 0) { + this.selectedUser = r.added[0].name; + } else { + this.selectedUser = ''; + } + this.updateWorkspaceList(); + }); + } + + ngOnInit() { + this.logindataSubscription = this.mds.loginData$.subscribe(ld => { + this.isSuperadmin = ld.is_superadmin; + this.updateObjectList(); + }); + } + + // *********************************************************************************** + addObject() { + const dialogRef = this.newuserDialog.open(NewuserComponent, { + width: '600px', + data: { + name: '' + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (typeof result !== 'undefined') { + if (result !== false) { + this.bs.addUser((<FormGroup>result).get('name').value, + (<FormGroup>result).get('pw').value).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Nutzer hinzugefügt', '', {duration: 1000}); + this.updateObjectList(); + } else { + this.snackBar.open('Konnte Nutzer nicht hinzufügen', 'Fehler', {duration: 1000}); + } + }); + } + } + }); + } + + changePassword() { + let selectedRows = this.tableselectionRow.selected; + if (selectedRows.length === 0) { + selectedRows = this.tableselectionCheckbox.selected; + } + if (selectedRows.length === 0) { + this.messsageDialog.open(MessageDialogComponent, { + width: '400px', + data: <MessageDialogData>{ + title: 'Kennwort ändern', + content: 'Bitte markieren Sie erst einen Nutzer!', + type: MessageType.error + } + }); + } else { + const dialogRef = this.newpasswordDialog.open(NewpasswordComponent, { + width: '600px', + data: { + name: selectedRows[0]['name'] + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (typeof result !== 'undefined') { + if (result !== false) { + this.dataLoading = true; + this.bs.changePassword(selectedRows[0]['name'], + (<FormGroup>result).get('pw').value).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Kennwort geändert', '', {duration: 1000}); + } else { + this.snackBar.open('Konnte Kennwort nicht ändern', 'Fehler', {duration: 1000}); + } + this.dataLoading = false; + }); + } + } + }); + } + } + + deleteObject() { + let selectedRows = this.tableselectionCheckbox.selected; + if (selectedRows.length === 0) { + selectedRows = this.tableselectionRow.selected; + } + if (selectedRows.length === 0) { + this.messsageDialog.open(MessageDialogComponent, { + width: '400px', + data: <MessageDialogData>{ + title: 'Löschen von Nutzern', + content: 'Bitte markieren Sie erst Nutzer!', + type: MessageType.error + } + }); + } else { + let prompt = 'Soll'; + if (selectedRows.length > 1) { + prompt = prompt + 'en ' + selectedRows.length + ' Nutzer '; + } else { + prompt = prompt + ' Nutzer "' + selectedRows[0].name + '" '; + } + const dialogRef = this.deleteConfirmDialog.open(ConfirmDialogComponent, { + width: '400px', + data: <ConfirmDialogData>{ + title: 'Löschen von Nutzern', + content: prompt + 'gelöscht werden?', + confirmbuttonlabel: 'Nutzer löschen', + showcancel: true + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result !== false) { + // ========================================================= + this.dataLoading = true; + const usersToDelete = []; + selectedRows.forEach((r: NameOnly) => usersToDelete.push(r.name)); + this.bs.deleteUsers(usersToDelete).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Nutzer gelöscht', '', {duration: 1000}); + this.updateObjectList(); + this.dataLoading = false; + } else { + this.snackBar.open('Konnte Nutzer nicht löschen', 'Fehler', {duration: 2000}); + this.dataLoading = false; + } + }); + } + }); + } + } + + // *********************************************************************************** + updateWorkspaceList() { + this.pendingWorkspaceChanges = false; + if (this.selectedUser.length > 0) { + this.dataLoading = true; + this.bs.getWorkspacesByUser(this.selectedUser).subscribe(dataresponse => { + this.WorkspacelistDatasource = new MatTableDataSource(dataresponse); + this.dataLoading = false; + }); + } else { + this.WorkspacelistDatasource = null; + } + } + + selectWorkspace(ws: IdRoleData, role: string) { + if (ws.role === role) { + ws.role = ''; + } else { + ws.role = role; + } + this.pendingWorkspaceChanges = true; + } + + saveWorkspaces() { + this.pendingWorkspaceChanges = false; + if (this.selectedUser.length > 0) { + this.dataLoading = true; + this.bs.setWorkspacesByUser(this.selectedUser, this.WorkspacelistDatasource.data).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Zugriffsrechte geändert', '', {duration: 1000}); + } else { + this.snackBar.open('Konnte Zugriffsrechte nicht ändern', 'Fehler', {duration: 2000}); + } + this.dataLoading = false; + }); + } else { + this.WorkspacelistDatasource = null; + } + } + + // *********************************************************************************** + updateObjectList() { + if (this.isSuperadmin) { + this.dataLoading = true; + this.tableselectionCheckbox.clear(); + this.tableselectionRow.clear(); + this.bs.getUsers().subscribe(dataresponse => { + this.objectsDatasource = new MatTableDataSource(dataresponse); + this.objectsDatasource.sort = this.sort; + this.dataLoading = false; + } + ); + } + } + + isAllSelected() { + const numSelected = this.tableselectionCheckbox.selected.length; + const numRows = this.objectsDatasource.data.length; + return numSelected === numRows; + } + + masterToggle() { + this.isAllSelected() ? + this.tableselectionCheckbox.clear() : + this.objectsDatasource.data.forEach(row => this.tableselectionCheckbox.select(row)); + } + + selectRow(row) { + this.tableselectionRow.select(row); + } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.logindataSubscription !== null) { + this.logindataSubscription.unsubscribe(); + } + } +} diff --git a/src/app/superadmin/workspaces/editworkspace/editworkspace.component.css b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/superadmin/workspaces/editworkspace/editworkspace.component.html b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.html new file mode 100644 index 0000000000000000000000000000000000000000..ea8a177147f15f24e8abc97b062d00d6f756d950 --- /dev/null +++ b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.html @@ -0,0 +1,17 @@ +<form [formGroup]="editworkspaceform"> + <h1 mat-dialog-title>Arbeitsbereich "{{data.oldname}}" ändern</h1> + + <mat-dialog-content> + <p> + <mat-form-field class="full-width"> + <input matInput formControlName="name" placeholder="Name" [value]="data.name"> + </mat-form-field> + </p> + </mat-dialog-content> + + <mat-dialog-actions> + <button mat-raised-button color="primary" type="submit" [mat-dialog-close]="editworkspaceform" [disabled]="editworkspaceform.invalid">Speichern</button> + <button mat-raised-button [mat-dialog-close]="false">Abbrechen</button> + </mat-dialog-actions> + +</form> diff --git a/src/app/superadmin/workspaces/editworkspace/editworkspace.component.spec.ts b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b464397d576671f928a425c97168ea1a556d723 --- /dev/null +++ b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditworkspaceComponent } from './editworkspace.component'; + +describe('EditworkspaceComponent', () => { + let component: EditworkspaceComponent; + let fixture: ComponentFixture<EditworkspaceComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditworkspaceComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditworkspaceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/workspaces/editworkspace/editworkspace.component.ts b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..55013ca244f4916715a8f6c2baa906dc17fe08ed --- /dev/null +++ b/src/app/superadmin/workspaces/editworkspace/editworkspace.component.ts @@ -0,0 +1,20 @@ +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Component, OnInit, Inject } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + templateUrl: './editworkspace.component.html', + styleUrls: ['./editworkspace.component.css'] +}) +export class EditworkspaceComponent implements OnInit { + editworkspaceform: FormGroup; + + constructor(private fb: FormBuilder, + @Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit() { + this.editworkspaceform = this.fb.group({ + name: this.fb.control('', [Validators.required, Validators.minLength(3)]) + }); + } +} diff --git a/src/app/superadmin/workspaces/newworkspace/newworkspace.component.css b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/superadmin/workspaces/newworkspace/newworkspace.component.html b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.html new file mode 100644 index 0000000000000000000000000000000000000000..5f6e7c282656460604295e5c11967e1cdc23b421 --- /dev/null +++ b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.html @@ -0,0 +1,21 @@ +<form [formGroup]="newworkspaceform"> + <h1 mat-dialog-title>Neuer Arbeitsbereich</h1> + + <mat-dialog-content> + <p> + <mat-form-field class="full-width"> + <input matInput formControlName="name" placeholder="Name" [value]="data.name"> + </mat-form-field> + </p> + <div class="infobox"> + <p>Nach dem Anlegen des Arbeitsbereiches können Sie die Zugriffsrechte zuweisen. + </p> + </div> + </mat-dialog-content> + + <mat-dialog-actions> + <button mat-raised-button color="primary" type="submit" [mat-dialog-close]="newworkspaceform" [disabled]="newworkspaceform.invalid">Speichern</button> + <button mat-raised-button [mat-dialog-close]="false">Abbrechen</button> + </mat-dialog-actions> + +</form> diff --git a/src/app/superadmin/workspaces/newworkspace/newworkspace.component.spec.ts b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2c645598677fe1b8f3ae62356d7f93912b3db31 --- /dev/null +++ b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewworkspaceComponent } from './newworkspace.component'; + +describe('NewworkspaceComponent', () => { + let component: NewworkspaceComponent; + let fixture: ComponentFixture<NewworkspaceComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NewworkspaceComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NewworkspaceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/workspaces/newworkspace/newworkspace.component.ts b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..02cf6f563b7dd474d94a3fd2546591baf9aaef28 --- /dev/null +++ b/src/app/superadmin/workspaces/newworkspace/newworkspace.component.ts @@ -0,0 +1,20 @@ +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Component, OnInit, Inject } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +@Component({ + templateUrl: './newworkspace.component.html', + styleUrls: ['./newworkspace.component.css'] +}) +export class NewworkspaceComponent implements OnInit { + newworkspaceform: FormGroup; + + constructor(private fb: FormBuilder, + @Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit() { + this.newworkspaceform = this.fb.group({ + name: this.fb.control('', [Validators.required, Validators.minLength(3)]) + }); + } +} diff --git a/src/app/superadmin/workspaces/workspaces.component.css b/src/app/superadmin/workspaces/workspaces.component.css new file mode 100644 index 0000000000000000000000000000000000000000..4c24b03c812046310b0988dea942ef0cdb956850 --- /dev/null +++ b/src/app/superadmin/workspaces/workspaces.component.css @@ -0,0 +1,8 @@ +.mat-raised-button { + min-width: 100px; + margin: 2px; +} + +.mat-checkbox { + margin: 0 3px; +} \ No newline at end of file diff --git a/src/app/superadmin/workspaces/workspaces.component.html b/src/app/superadmin/workspaces/workspaces.component.html new file mode 100644 index 0000000000000000000000000000000000000000..289abc7259fb8fcbde429c7015c55bcfc0bbcc17 --- /dev/null +++ b/src/app/superadmin/workspaces/workspaces.component.html @@ -0,0 +1,85 @@ +<div class="columnhost" fxLayout="row" fxLayoutAlign="space-between start"> + <div class="spinner-container" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + + <!-- ============================================= --> + <div class="objectlist" fxLayout="column" fxFlex="50"> + <div fxLayout="row"> + <button mat-raised-button (click)="addObject()" matTooltip="Arbeitsbereich hinzufügen" matTooltipPosition="above"> + <mat-icon>add</mat-icon> + </button> + <button mat-raised-button (click)="deleteObject()" matTooltip="Markierte/n Arbeitsbereich/e löschen" matTooltipPosition="above"> + <mat-icon>delete</mat-icon> + </button> + <button mat-raised-button (click)="changeObject()" matTooltip="Arbeitsbereich umbenennen" matTooltipPosition="above"> + <mat-icon>edit</mat-icon> + </button> + </div> + + <mat-table *ngIf="isSuperadmin" [dataSource]="objectsDatasource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef fxFlex="70px"> + <mat-checkbox (change)="$event ? masterToggle() : null" + [checked]="tableselectionCheckbox.hasValue() && isAllSelected()" + [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()"> + </mat-checkbox> + </mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="70px"> + <mat-checkbox (click)="$event.stopPropagation()" + (change)="$event ? tableselectionCheckbox.toggle(row) : null" + [checked]="tableselectionCheckbox.isSelected(row)"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="name"> + <mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell> + <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;" (click)="selectRow(row)" + [style.background]="tableselectionRow.isSelected(row) ? 'lightblue' : ''"></mat-row> + </mat-table> + </div> + + <!-- ============================================= --> + <div *ngIf="isSuperadmin" fxLayout="column" fxFlex="40"> + + <div *ngIf="selectedWorkspaceId == 0"> + <div>Zugriffsberechtigte für Arbeitsbereich:</div> + <div>Bitte links einen Arbeitsbereich wählen</div> + </div> + + <div *ngIf="selectedWorkspaceId > 0" fxLayout="row" fxLayoutAlign="space-between center"> + <div>Zugriffsrechte für "{{ selectedWorkspaceName }}":</div> + <button mat-raised-button (click)="saveUsers()" matTooltip="Speichern" + matTooltipPosition="above" [disabled]="!pendingUserChanges"> + <mat-icon>save</mat-icon> + </button> + </div> + + <mat-table [dataSource]="UserlistDatasource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef mat-sort-header>RO | RW | MO</mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="100px"> + <mat-checkbox (change)="selectUser(row, 'RO')" [checked]="row.role === 'RO'" matTooltip="RO"> + </mat-checkbox> + <mat-checkbox (change)="selectUser(row, 'RW')" [checked]="row.role === 'RW'" matTooltip="RW"> + </mat-checkbox> + <mat-checkbox (change)="selectUser(row, 'MO')" [checked]="row.role === 'MO'" matTooltip="MO"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="name"> + <mat-header-cell *matHeaderCellDef mat-sort-header> Nutzer </mat-header-cell> + <mat-cell *matCellDef="let row"> {{row.name}} </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedUserColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedUserColumns;"></mat-row> + </mat-table> + </div> +</div> diff --git a/src/app/superadmin/workspaces/workspaces.component.spec.ts b/src/app/superadmin/workspaces/workspaces.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..36be59aab54ec2b9163c8efa5870b62e2c9ea71d --- /dev/null +++ b/src/app/superadmin/workspaces/workspaces.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkspacesComponent } from './workspaces.component'; + +describe('WorkspacesComponent', () => { + let component: WorkspacesComponent; + let fixture: ComponentFixture<WorkspacesComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkspacesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkspacesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/superadmin/workspaces/workspaces.component.ts b/src/app/superadmin/workspaces/workspaces.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..96dac73a4454595822233fffdb72c4fdc92503b3 --- /dev/null +++ b/src/app/superadmin/workspaces/workspaces.component.ts @@ -0,0 +1,273 @@ +import { EditworkspaceComponent } from './editworkspace/editworkspace.component'; +import { NewworkspaceComponent } from './newworkspace/newworkspace.component'; +import { BackendService, IdAndName, IdRoleData } from '../backend.service'; +import { MatTableDataSource } from '@angular/material/table'; +import { ViewChild, OnDestroy } from '@angular/core'; + +import { Component, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatSort } from '@angular/material/sort'; +import { FormGroup } from '@angular/forms'; +import { SelectionModel } from '@angular/cdk/collections'; +import { + ConfirmDialogComponent, ConfirmDialogData, + MessageDialogComponent, MessageDialogData, MessageType +} from 'iqb-components'; +import { Subscription } from 'rxjs'; +import { MainDataService } from 'src/app/maindata.service'; + +@Component({ + templateUrl: './workspaces.component.html', + styleUrls: ['./workspaces.component.css'] +}) +export class WorkspacesComponent implements OnInit, OnDestroy { + public isSuperadmin = false; + public dataLoading = false; + public objectsDatasource: MatTableDataSource<IdAndName>; + public displayedColumns = ['selectCheckbox', 'name']; + private tableselectionCheckbox = new SelectionModel <IdAndName>(true, []); + private tableselectionRow = new SelectionModel <IdAndName>(false, []); + private selectedWorkspaceId = 0; + private selectedWorkspaceName = ''; + private logindataSubscription: Subscription = null; + + private pendingUserChanges = false; + public UserlistDatasource: MatTableDataSource<IdRoleData>; + public displayedUserColumns = ['selectCheckbox', 'name']; + + @ViewChild(MatSort, { static: false }) sort: MatSort; + + constructor( + private bs: BackendService, + private mds: MainDataService, + private newworkspaceDialog: MatDialog, + private editworkspaceDialog: MatDialog, + private deleteConfirmDialog: MatDialog, + private messsageDialog: MatDialog, + private snackBar: MatSnackBar + ) { + this.tableselectionRow.changed.subscribe( + r => { + if (r.added.length > 0) { + this.selectedWorkspaceId = r.added[0].id; + this.selectedWorkspaceName = r.added[0].name; + } else { + this.selectedWorkspaceId = 0; + this.selectedWorkspaceName = ''; + } + this.updateUserList(); + }); + } + + ngOnInit() { + this.logindataSubscription = this.mds.loginData$.subscribe(ld => { + this.isSuperadmin = ld.is_superadmin; + this.updateObjectList(); + }); + } + + // *********************************************************************************** + addObject() { + const dialogRef = this.newworkspaceDialog.open(NewworkspaceComponent, { + width: '600px', + data: { + name: '' + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (typeof result !== 'undefined') { + if (result !== false) { + this.dataLoading = true; + this.bs.addWorkspace((<FormGroup>result).get('name').value).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Arbeitsbereich hinzugefügt', '', {duration: 1000}); + this.updateObjectList(); + } else { + this.snackBar.open('Konnte Arbeitsbereich nicht hinzufügen', 'Fehler', {duration: 1000}); + } + this.dataLoading = false; + }); + } + } + }); + } + + changeObject() { + let selectedRows = this.tableselectionRow.selected; + if (selectedRows.length === 0) { + selectedRows = this.tableselectionCheckbox.selected; + } + if (selectedRows.length === 0) { + this.messsageDialog.open(MessageDialogComponent, { + width: '400px', + data: <MessageDialogData>{ + title: 'Arbeitsbereich ändern', + content: 'Bitte markieren Sie erst einen Arbeitsbereich!', + type: MessageType.error + } + }); + } else { + const dialogRef = this.editworkspaceDialog.open(EditworkspaceComponent, { + width: '600px', + data: { + name: selectedRows[0].name, + oldname: selectedRows[0].name + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (typeof result !== 'undefined') { + if (result !== false) { + this.dataLoading = true; + this.bs.renameWorkspace(selectedRows[0].id, + (<FormGroup>result).get('name').value).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Arbeitsbereich geändert', '', {duration: 1000}); + this.updateObjectList(); + } else { + this.snackBar.open('Konnte Arbeitsbereich nicht ändern', 'Fehler', {duration: 2000}); + } + this.dataLoading = false; + }); + } + } + }); + } + } + + deleteObject() { + let selectedRows = this.tableselectionCheckbox.selected; + if (selectedRows.length === 0) { + selectedRows = this.tableselectionRow.selected; + } + if (selectedRows.length === 0) { + this.messsageDialog.open(MessageDialogComponent, { + width: '400px', + data: <MessageDialogData>{ + title: 'Löschen von Arbeitsbereichen', + content: 'Bitte markieren Sie erst Arbeitsbereich/e!', + type: MessageType.error + } + }); + } else { + let prompt = 'Soll'; + if (selectedRows.length > 1) { + prompt = prompt + 'en ' + selectedRows.length + ' Arbeitsbereiche '; + } else { + prompt = prompt + ' Arbeitsbereich "' + selectedRows[0].name + '" '; + } + const dialogRef = this.deleteConfirmDialog.open(ConfirmDialogComponent, { + width: '400px', + data: <ConfirmDialogData>{ + title: 'Löschen von Arbeitsbereichen', + content: prompt + 'gelöscht werden?', + confirmbuttonlabel: 'Arbeitsbereich/e löschen', + showcancel: true + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result !== false) { + // ========================================================= + this.dataLoading = true; + const workspacesToDelete = []; + selectedRows.forEach((r: IdAndName) => workspacesToDelete.push(r.id)); + this.bs.deleteWorkspaces(workspacesToDelete).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Arbeitsbereich/e gelöscht', '', {duration: 1000}); + this.updateObjectList(); + this.dataLoading = false; + } else { + this.snackBar.open('Konnte Arbeitsbereich/e nicht löschen', 'Fehler', {duration: 1000}); + this.dataLoading = false; + } + }); + } + }); + } + } + + // *********************************************************************************** + updateUserList() { + this.pendingUserChanges = false; + if (this.selectedWorkspaceId > 0) { + this.dataLoading = true; + this.bs.getUsersByWorkspace(this.selectedWorkspaceId).subscribe(dataresponse => { + this.UserlistDatasource = new MatTableDataSource(dataresponse); + this.dataLoading = false; + }); + } else { + this.UserlistDatasource = null; + } + } + + selectUser(ws: IdRoleData, role: string) { + if (ws.role === role) { + ws.role = ''; + } else { + ws.role = role; + } + this.pendingUserChanges = true; + } + + saveUsers() { + this.pendingUserChanges = false; + if (this.selectedWorkspaceId > 0) { + this.dataLoading = true; + this.bs.setUsersByWorkspace(this.selectedWorkspaceId, this.UserlistDatasource.data).subscribe( + respOk => { + if (respOk) { + this.snackBar.open('Zugriffsrechte geändert', '', {duration: 1000}); + } else { + this.snackBar.open('Konnte Zugriffsrechte nicht ändern', 'Fehler', {duration: 2000}); + } + this.dataLoading = false; + }); + } else { + this.UserlistDatasource = null; + } + } + + // *********************************************************************************** + updateObjectList() { + if (this.isSuperadmin) { + this.dataLoading = true; + this.bs.getWorkspaces().subscribe(dataresponse => { + this.objectsDatasource = new MatTableDataSource(dataresponse); + this.objectsDatasource.sort = this.sort; + this.tableselectionCheckbox.clear(); + this.tableselectionRow.clear(); + this.dataLoading = false; + } + ); + } + } + + isAllSelected() { + const numSelected = this.tableselectionCheckbox.selected.length; + const numRows = this.objectsDatasource.data.length; + return numSelected === numRows; + } + + masterToggle() { + this.isAllSelected() ? + this.tableselectionCheckbox.clear() : + this.objectsDatasource.data.forEach(row => this.tableselectionCheckbox.select(row)); + } + + selectRow(row) { + this.tableselectionRow.select(row); + } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.logindataSubscription !== null) { + this.logindataSubscription.unsubscribe(); + } + } +} diff --git a/src/app/workspace/backend.service.spec.ts b/src/app/workspace/backend.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c31039d7e5bcc7bc70e7213ce06744a0dc6ed009 --- /dev/null +++ b/src/app/workspace/backend.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { BackendService } from './backend.service'; + +describe('BackendService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [BackendService] + }); + }); + + it('should be created', inject([BackendService], (service: BackendService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/workspace/backend.service.ts b/src/app/workspace/backend.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..66d41e4ba047c4736204d46023d58b6f256873d5 --- /dev/null +++ b/src/app/workspace/backend.service.ts @@ -0,0 +1,154 @@ +import { GetFileResponseData, CheckWorkspaceResponseData, BookletsStarted, SysCheckStatistics, + ReviewData, LogData, UnitResponse, ResultData, MonitorData } from './workspace.interfaces'; +import {Injectable, Inject} from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import {WorkspaceDataService} from "./workspacedata.service"; +import {MainDataService} from "../maindata.service"; +import { ErrorHandler, ServerError } from 'iqb-components'; + +@Injectable() + +export class BackendService { + private serverUrlSlim = ''; + private serverUrlSysCheck = ''; + + constructor( + @Inject('SERVER_URL') private readonly serverUrl: string, + private http: HttpClient, + private wds: WorkspaceDataService, + private mds: MainDataService) { + + this.serverUrlSlim = this.serverUrl + 'php/ws.php/'; + this.serverUrlSysCheck = this.serverUrl + 'php_admin/'; + this.serverUrl = this.serverUrl + 'php/'; + } + + + getFiles(): Observable<GetFileResponseData[] | ServerError> { + return this.http + .get<GetFileResponseData[]>(this.serverUrlSlim + 'filelist') + .pipe( + catchError(ErrorHandler.handle) + ); + } + + deleteFiles(filesToDelete: Array<string>): Observable<string | ServerError> { + return this.http + .post<string>(this.serverUrlSlim + 'delete', {f: filesToDelete}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + checkWorkspace(): Observable<CheckWorkspaceResponseData | ServerError> { + return this.http + .post<CheckWorkspaceResponseData>(this.serverUrl + 'checkWorkspace.php', {}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + getBookletsStarted(groups: string[]): Observable<BookletsStarted[] | ServerError> { + return this.http + .post<BookletsStarted[]>(this.serverUrl + 'getBookletsStarted.php', {g: groups}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + lockBooklets(groups: string[]): Observable<boolean | ServerError> { + return this.http + .post<boolean>(this.serverUrlSlim + 'lock', {g: groups}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + unlockBooklets(groups: string[]): Observable<boolean | ServerError> { + return this.http + .post<boolean>(this.serverUrlSlim + 'unlock', {g: groups}) + .pipe( + catchError(ErrorHandler.handle) + ); +} + + getMonitorData(): Observable<MonitorData[] | ServerError> { + return this.http + .post<MonitorData[]>(this.serverUrl + 'getMonitorData.php', {}) + .pipe( + catchError(ErrorHandler.handle) + ); +} + + getResultData(): Observable<ResultData[]> { + return this.http + .post<ResultData[]>(this.serverUrl + 'getResultData.php', {}) + .pipe( + catchError(() => []) + ); + } + + getResponses(groups: string[]): Observable<UnitResponse[]> { + return this.http + .post<UnitResponse[]>(this.serverUrl + 'getResponses.php', {g: groups}) + .pipe( + catchError(() => []) + ); + } + + getLogs(groups: string[]): Observable<LogData[]> { + return this.http + .post<LogData[]>(this.serverUrl + 'getLogs.php', {g: groups}) + .pipe( + catchError(() => []) + ); + } + + getReviews(groups: string[]): Observable<ReviewData[]> { + return this.http + .post<ReviewData[]>(this.serverUrl + 'getReviews.php', {g: groups}) + .pipe( + catchError(() => []) + ); + } + + deleteData(groups: string[]): Observable<boolean | ServerError> { + return this.http + .post<boolean>(this.serverUrl + 'deleteData.php', {g: groups}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + getSysCheckReportList(): Observable<SysCheckStatistics[] | ServerError> { + const loginData = this.mds.loginData$.getValue(); + return this.http + .post<SysCheckStatistics[]>(this.serverUrlSysCheck + 'getSysCheckReportList.php', {ws: this.wds.workspaceId$.getValue(), at: loginData.admintoken}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + getSysCheckReport(reports: string[], columnDelimiter: string, + quoteChar: string): Observable<string[] | ServerError> { + const loginData = this.mds.loginData$.getValue(); + return this.http + .post<string[]>(this.serverUrlSysCheck + 'getSysCheckReport.php', + {r: reports, cd: columnDelimiter, q: quoteChar, ws: this.wds.workspaceId$.getValue(), at: loginData.admintoken}) + .pipe( + catchError(ErrorHandler.handle) + ); + } + + deleteSysCheckReports(reports: string[]): Observable<boolean | ServerError> { + const loginData = this.mds.loginData$.getValue(); + return this.http + .post<boolean>(this.serverUrlSysCheck + 'deleteSysCheckReports.php', + {r: reports, ws: this.wds.workspaceId$.getValue(), at: loginData.admintoken}) + .pipe( + catchError(ErrorHandler.handle) + ); + } +} diff --git a/src/app/workspace/files/files.component.css b/src/app/workspace/files/files.component.css new file mode 100644 index 0000000000000000000000000000000000000000..58d6fe8541b8a2b32ef209f2b4299205905da969 --- /dev/null +++ b/src/app/workspace/files/files.component.css @@ -0,0 +1,50 @@ +.columnhost { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: left; +} + +.filelist { + flex: 10 0 400px; +} + +.checkboxcell { + overflow: visible; + flex: 0 0 30px; +} + +.namecell { + flex: 3 3 60px; +} + +.datecell { + flex: 1 1 5px; +} + +.uploads { + flex: 10 0 200px; +} + + +.checkerror, .checkwarning, .checkinfo { + margin: 20px; + font-size: 0.8em; +} + +.checkerror { + color: brown; +} +.checkwarning { + color: goldenrod; +} +.checkinfo { + color: darkgreen; +} + +.mat-raised-button { + min-width: 100px; + margin: 2px; +} \ No newline at end of file diff --git a/src/app/workspace/files/files.component.html b/src/app/workspace/files/files.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d611835af34b3f5ddbc5edd9702248b87f47c937 --- /dev/null +++ b/src/app/workspace/files/files.component.html @@ -0,0 +1,83 @@ +<div class="columnhost"> + <div class="spinner-container" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + + <!-- ============================================= --> + <div class="filelist"> + <mat-table #table [dataSource]="serverfiles" matSort> + <ng-container matColumnDef="checked"> + <mat-header-cell *matHeaderCellDef class="checkboxcell"> + <mat-checkbox (change)="checkAll($event.checked)"></mat-checkbox> + </mat-header-cell> + <mat-cell *matCellDef="let element" class="checkboxcell"> + <mat-checkbox [checked]="element.isChecked" (change)="element.isChecked=$event.checked"></mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="typelabel"> + <mat-header-cell *matHeaderCellDef mat-sort-header> Typ </mat-header-cell> + <mat-cell *matCellDef="let element"> {{element.typelabel}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="filename"> + <mat-header-cell *matHeaderCellDef mat-sort-header class="namecell"> Name </mat-header-cell> + <mat-cell *matCellDef="let element" class="namecell"><a target="_blank" [href]="getDownloadRef(element)">{{element.filename}}</a> </mat-cell> + </ng-container> + + <ng-container matColumnDef="filedatetime"> + <mat-header-cell *matHeaderCellDef mat-sort-header class="datecell"> Datum </mat-header-cell> + <mat-cell *matCellDef="let element" class="datecell"> {{element.filedatetimestr}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="filesize"> + <mat-header-cell *matHeaderCellDef mat-sort-header> Größe </mat-header-cell> + <mat-cell *matCellDef="let element"> {{element.filesizestr}} </mat-cell> + </ng-container> + + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> + </div> + + <!-- ============================================= --> + <div class="uploads"> + <button mat-raised-button (click)="deleteFiles()" matTooltip="Markierte Dateien löschen" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'"> + <mat-icon>delete</mat-icon> + </button> + <button mat-raised-button (click)="hiddenfileinput.click()" matTooltip="Dateien hochladen/aktualisieren" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'"> + <mat-icon>cloud_upload</mat-icon> + </button> + <button mat-raised-button (click)="checkWorkspace()" matTooltip="Arbeitsbereich prüfen" matTooltipPosition="above"> + <mat-icon>check</mat-icon> + </button> + + <input #hiddenfileinput type="file" name="fileforvo" multiple [iqbFilesUploadInputFor]="fileUploadQueue" [hidden]="true"/> + + <iqb-files-upload-queue #fileUploadQueue + [httpUrl]="uploadUrl" + [fileAlias]="fileNameAlias" + [tokenName]="'at'" + [token]="'kisduUjjw.;kiskw..9200'" + [folderName]="'ws'" + [folder]="'workspace'" + (uploadCompleteEvent)="updateFileList()"> + </iqb-files-upload-queue> + + <p *ngFor="let e of checkErrors" class="checkerror"> + {{ e }} + </p> + + <p *ngFor="let w of checkWarnings" class="checkwarning"> + {{ w }} + </p> + + <p *ngFor="let i of checkInfos" class="checkinfo"> + {{ i }} + </p> + + <br/> + + </div> +</div> diff --git a/src/app/workspace/files/files.component.spec.ts b/src/app/workspace/files/files.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..da941cc1423eb4e0ccee04599f7a30827265304c --- /dev/null +++ b/src/app/workspace/files/files.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilesComponent } from './files.component'; + +describe('FilesComponent', () => { + let component: FilesComponent; + let fixture: ComponentFixture<FilesComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FilesComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FilesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/workspace/files/files.component.ts b/src/app/workspace/files/files.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..45445a2ef5034daf47fa8652a99e1337c915478f --- /dev/null +++ b/src/app/workspace/files/files.component.ts @@ -0,0 +1,186 @@ +import { MainDataService } from '../../maindata.service'; +import { WorkspaceDataService } from '../workspacedata.service'; +import { GetFileResponseData, CheckWorkspaceResponseData } from '../workspace.interfaces'; +import { ConfirmDialogComponent, ConfirmDialogData, MessageDialogComponent, + MessageDialogData, MessageType, ServerError } from 'iqb-components'; +import { Subscription } from 'rxjs'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { BackendService } from '../backend.service'; +import { Component, OnInit, Inject, OnDestroy } from '@angular/core'; +import { ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSort } from '@angular/material/sort'; + +@Component({ + templateUrl: './files.component.html', + styleUrls: ['./files.component.css'] +}) +export class FilesComponent implements OnInit, OnDestroy { + public serverfiles: MatTableDataSource<GetFileResponseData>; + public displayedColumns = ['checked', 'filename', 'typelabel', 'filesize', 'filedatetime']; + public dataLoading = false; + private workspaceIdSubscription: Subscription = null; + + // for fileupload + public uploadUrl = ''; + public fileNameAlias = 'fileforvo'; + + // for workspace-check + public checkErrors = []; + public checkWarnings = []; + public checkInfos = []; + + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + @Inject('SERVER_URL') private serverUrl: string, + private bs: BackendService, + private mds: MainDataService, + public wds: WorkspaceDataService, + public confirmDialog: MatDialog, + public messsageDialog: MatDialog, + public snackBar: MatSnackBar + ) { + this.uploadUrl = this.serverUrl + 'php/uploadFile.php'; + } + + ngOnInit() { + this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(() => { + this.updateFileList((this.wds.ws <= 0) || (this.mds.adminToken.length === 0)); + }); + } + + // *********************************************************************************** + checkAll(isChecked: boolean) { + this.serverfiles.data.forEach(element => { + element.isChecked = isChecked; + }); + } + + // *********************************************************************************** + deleteFiles() { + if (this.wds.wsRole === 'RW') { + this.checkErrors = []; + this.checkWarnings = []; + this.checkInfos = []; + + const filesToDelete = []; + this.serverfiles.data.forEach(element => { + if (element.isChecked) { + filesToDelete.push(element.type + '::' + element.filename); + } + }); + + if (filesToDelete.length > 0) { + let prompt = 'Sie haben '; + if (filesToDelete.length > 1) { + prompt = prompt + filesToDelete.length + ' Dateien ausgewählt. Sollen'; + } else { + prompt = prompt + ' eine Datei ausgewählt. Soll'; + } + const dialogRef = this.confirmDialog.open(ConfirmDialogComponent, { + width: '400px', + data: <ConfirmDialogData>{ + title: 'Löschen von Dateien', + content: prompt + ' diese gelöscht werden?', + confirmbuttonlabel: 'Löschen', + showcancel: true + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result !== false) { + // ========================================================= + this.dataLoading = true; + this.bs.deleteFiles(filesToDelete).subscribe(deletefilesresponse => { + if (deletefilesresponse instanceof ServerError) { + this.wds.setNewErrorMsg(deletefilesresponse as ServerError); + } else { + const deletefilesresponseOk = deletefilesresponse as string; + if ((deletefilesresponseOk.length > 5) && (deletefilesresponseOk.substr(0, 2) === 'e:')) { + this.snackBar.open(deletefilesresponseOk.substr(2), 'Fehler', {duration: 1000}); + } else { + this.snackBar.open(deletefilesresponseOk, '', {duration: 1000}); + this.updateFileList(); + } + this.wds.setNewErrorMsg(); + } + }); + // ========================================================= + } + }); + } else { + this.messsageDialog.open(MessageDialogComponent, { + width: '400px', + data: <MessageDialogData>{ + title: 'Löschen von Dateien', + content: 'Bitte markieren Sie erst Dateien!', + type: MessageType.error + } + }); + } + } + } + + // *********************************************************************************** + updateFileList(empty = false) { + this.checkErrors = []; + this.checkWarnings = []; + this.checkInfos = []; + + if (empty || this.wds.wsRole === 'MO') { + this.serverfiles = new MatTableDataSource([]); + } else { + this.dataLoading = true; + this.bs.getFiles().subscribe( + (filedataresponse: GetFileResponseData[]) => { + this.serverfiles = new MatTableDataSource(filedataresponse); + this.serverfiles.sort = this.sort; + this.dataLoading = false; + this.wds.setNewErrorMsg(); + }, (err: ServerError) => { + this.wds.setNewErrorMsg(err); + this.dataLoading = false; + } + ); + } + } + + // *********************************************************************************** + getDownloadRef(element: GetFileResponseData): string { + return this.serverUrl + + 'php/getFile.php?t=' + element.type + + '&fn=' + element.filename + + '&at=' + this.mds.adminToken + + '&ws=' + this.wds.ws.toString(); + } + + checkWorkspace() { + this.checkErrors = []; + this.checkWarnings = []; + this.checkInfos = []; + + this.dataLoading = true; + this.bs.checkWorkspace().subscribe( + (checkResponse: CheckWorkspaceResponseData) => { + this.checkErrors = checkResponse.errors; + this.checkWarnings = checkResponse.warnings; + this.checkInfos = checkResponse.infos; + this.wds.setNewErrorMsg(); + + this.dataLoading = false; + }, (err: ServerError) => { + this.wds.setNewErrorMsg(err); + this.dataLoading = false; + } + ); + } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.workspaceIdSubscription !== null) { + this.workspaceIdSubscription.unsubscribe(); + } + } +} diff --git a/src/app/workspace/index.ts b/src/app/workspace/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..30c7aa4c2daa6ada54bf85e7876130ae495ef6a5 --- /dev/null +++ b/src/app/workspace/index.ts @@ -0,0 +1,3 @@ +export { WorkspaceComponent } from './workspace.component'; +export { WorkspaceModule } from './workspace.module'; +export { WorkspaceDataService } from './workspacedata.service'; diff --git a/src/app/workspace/monitor/monitor.component.css b/src/app/workspace/monitor/monitor.component.css new file mode 100644 index 0000000000000000000000000000000000000000..1c9da90e99d273d82175a86823b7ba97bbd0fc99 --- /dev/null +++ b/src/app/workspace/monitor/monitor.component.css @@ -0,0 +1,29 @@ +.columnhost { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: left; +} +/* .mat-card { + max-width: 50%; + margin-top: 20px; +} + +.title { + margin-top: 2550px; +} */ + +/* table { + width: 100%; +} */ + +.cellstyle1 { + padding: 5px; +} + +.mat-raised-button { + margin: 5px; + min-width: 100px; +} \ No newline at end of file diff --git a/src/app/workspace/monitor/monitor.component.html b/src/app/workspace/monitor/monitor.component.html new file mode 100644 index 0000000000000000000000000000000000000000..6fbe2f3a22d222199e4a6ed986aa125fc889906e --- /dev/null +++ b/src/app/workspace/monitor/monitor.component.html @@ -0,0 +1,75 @@ +<div class="columnhost" fxLayout="column"> + <div class="spinner-container" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + <div fxLayout="row"> + <button mat-raised-button (click)="downloadCSV()" [disabled]="!tableselectionCheckbox.hasValue()" + matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above"> + <mat-icon>cloud_download</mat-icon> + </button> + <button mat-raised-button (click)="lock()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')" + matTooltip="Sperre gestartete Testhefte für markierte Gruppen" matTooltipPosition="above"> + <mat-icon>lock</mat-icon> + </button> + + <button mat-raised-button (click)="unlock()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')" + matTooltip="Gesperrte Testhefte für markierte Gruppen freigeben" matTooltipPosition="above"> + <mat-icon>lock_open</mat-icon> + </button> + </div> + + <mat-table [dataSource]="monitorDataSource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef fxFlex="70px"> + <mat-checkbox (change)="$event ? masterToggle() : null" + [checked]="tableselectionCheckbox.hasValue() && isAllSelected()" + [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()"> + </mat-checkbox> + </mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="70px"> + <mat-checkbox (click)="$event.stopPropagation()" + (change)="$event ? tableselectionCheckbox.toggle(row) : null" + [checked]="tableselectionCheckbox.isSelected(row)"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="groupname"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxFlex="300px">Login-Gruppe</mat-header-cell> + <mat-cell *matCellDef="let element" fxFlex="300px">{{element.groupname}}</mat-cell> + </ng-container> + + <ng-container matColumnDef="loginsPrepared"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Logins</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.loginsPrepared}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="personsPrepared"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Personen</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.personsPrepared}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="bookletsPrepared"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Testhefte insges.</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.bookletsPrepared}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="bookletsStarted"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Testhefte gestartet</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.bookletsStarted}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="bookletsLocked"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">gesperrte Testhefte</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.bookletsLocked }} </mat-cell> + </ng-container> + + <ng-container matColumnDef="laststart"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Letzter Start</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.laststartStr }} </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> +</div> diff --git a/src/app/workspace/monitor/monitor.component.spec.ts b/src/app/workspace/monitor/monitor.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..18aec0382cd2c882ebf8799916e54a679397476b --- /dev/null +++ b/src/app/workspace/monitor/monitor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MonitorComponent } from './monitor.component'; + +describe('MonitorComponent', () => { + let component: MonitorComponent; + let fixture: ComponentFixture<MonitorComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MonitorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MonitorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/workspace/monitor/monitor.component.ts b/src/app/workspace/monitor/monitor.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8a28b07ba2276bd6bbf2dac76931e5c3e0a4581 --- /dev/null +++ b/src/app/workspace/monitor/monitor.component.ts @@ -0,0 +1,159 @@ +import { BookletsStarted } from '../workspace.interfaces'; +import { WorkspaceDataService } from '../workspacedata.service'; +import { BackendService } from '../backend.service'; +import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { SelectionModel } from '@angular/cdk/collections'; +import { saveAs } from 'file-saver'; +import { MonitorData } from '../workspace.interfaces'; +import { Subscription } from 'rxjs'; +import { ServerError } from 'iqb-components'; + + +@Component({ + templateUrl: './monitor.component.html', + styleUrls: ['./monitor.component.css'] +}) +export class MonitorComponent implements OnInit, OnDestroy { + + displayedColumns: string[] = ['selectCheckbox', 'groupname', 'loginsPrepared', + 'personsPrepared', 'bookletsPrepared', 'bookletsStarted', 'bookletsLocked', 'laststart']; + public monitorDataSource = new MatTableDataSource<MonitorData>([]); + public tableselectionCheckbox = new SelectionModel<MonitorData>(true, []); + private workspaceIdSubscription: Subscription = null; + public dataLoading = false; + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + private bs: BackendService, + public wds: WorkspaceDataService, + public snackBar: MatSnackBar + ) { } + + ngOnInit() { + this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(() => { + this.updateTable(); + }); + } + + updateTable() { + this.dataLoading = true; + this.tableselectionCheckbox.clear(); + this.bs.getMonitorData().subscribe( + (monitorData: MonitorData[]) => { + this.dataLoading = false; + this.monitorDataSource = new MatTableDataSource<MonitorData>(monitorData); + this.monitorDataSource.sort = this.sort; + } + ); + } + + isAllSelected() { + const numSelected = this.tableselectionCheckbox.selected.length; + const numRows = this.monitorDataSource.data.length; + return numSelected === numRows; + } + + masterToggle() { + this.isAllSelected() ? + this.tableselectionCheckbox.clear() : + this.monitorDataSource.data.forEach(row => this.tableselectionCheckbox.select(row)); + } + + downloadCSV() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + this.bs.getBookletsStarted(selectedGroups).subscribe(bData => { + + const bookletList = bData as BookletsStarted[]; + if (bookletList.length > 0) { + const columnDelimiter = ';'; + const lineDelimiter = '\n'; + + let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + + 'bookletname' + columnDelimiter + 'locked' + columnDelimiter + 'laststart' + lineDelimiter; + bookletList.forEach((b: BookletsStarted) => { + myCsvData += '"' + + b.groupname + '"' + columnDelimiter + '"' + + b.loginname + '"' + columnDelimiter + '"' + + b.code + '"' + columnDelimiter + '"' + + b.bookletname + '"' + columnDelimiter + + '"' + (b.locked ? 'X' : '-') + '"' + columnDelimiter + '"' + + b.laststart + '"' + lineDelimiter; + }); + const blob = new Blob([myCsvData], {type: 'text/csv;charset=utf-8'}); + saveAs(blob, 'iqb-testcenter-bookletsStarted.csv'); + } else { + this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000}); + } + + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + } + ); + } + } + + lock() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + this.bs.lockBooklets(selectedGroups).subscribe(success => { + if (success instanceof ServerError) { + this.wds.setNewErrorMsg(success as ServerError); + this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Systemfehler', {duration: 3000}); + } else { + const ok = success as boolean; + if (ok) { + this.snackBar.open('Testhefte wurden gesperrt.', 'Sperrung', {duration: 1000}); + } else { + this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Fehler', {duration: 3000}); + } + } + this.dataLoading = false; + this.updateTable(); + }); + } + } + + unlock() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + this.bs.unlockBooklets(selectedGroups).subscribe(success => { + if (success instanceof ServerError) { + this.wds.setNewErrorMsg(success as ServerError); + this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Systemfehler', {duration: 3000}); + } else { + const ok = success as boolean; + if (ok) { + this.snackBar.open('Testhefte wurden freigegeben.', 'Sperrung', {duration: 1000}); + } else { + this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Fehler', {duration: 3000}); + } + } + this.dataLoading = false; + this.updateTable(); + }); + } + } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.workspaceIdSubscription !== null) { + this.workspaceIdSubscription.unsubscribe(); + } + } +} diff --git a/src/app/workspace/results/results.component.css b/src/app/workspace/results/results.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e43a89f4bc4ed0d6e22ce350432e61e6fa1c5e8e --- /dev/null +++ b/src/app/workspace/results/results.component.css @@ -0,0 +1,17 @@ +/* .columnhost { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: left; +} */ + +/* .mat-icon { + margin-right: 5px; +} */ + +.mat-raised-button { + min-width: 100px; + margin: 2px; +} \ No newline at end of file diff --git a/src/app/workspace/results/results.component.html b/src/app/workspace/results/results.component.html new file mode 100644 index 0000000000000000000000000000000000000000..5ac0ebf46d395908c958eeef01f0cde816964f7d --- /dev/null +++ b/src/app/workspace/results/results.component.html @@ -0,0 +1,73 @@ +<div class="columnhost" fxLayout="column"> + <div class="spinner-container" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + <div fxLayout="row"> + <button mat-raised-button (click)="downloadResponsesCSV()" [disabled]="!tableselectionCheckbox.hasValue()" + matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above"> + <mat-icon>cloud_download</mat-icon>Antworten + </button> + <button mat-raised-button (click)="downloadLogsCSV()" [disabled]="!tableselectionCheckbox.hasValue()" + matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above"> + <mat-icon>cloud_download</mat-icon>Logs + </button> + <button mat-raised-button (click)="downloadReviewsCSV()" [disabled]="!tableselectionCheckbox.hasValue()" + matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above"> + <mat-icon>cloud_download</mat-icon>Kommentare + </button> + <button mat-raised-button (click)="deleteData()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')" + matTooltip="Löschen Ergebnisdaten aus der Datenbank für markierte Gruppen" matTooltipPosition="above"> + <mat-icon>delete</mat-icon> + </button> + </div> + + <mat-table [dataSource]="resultDataSource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef fxFlex="70px"> + <mat-checkbox (change)="$event ? masterToggle() : null" + [checked]="tableselectionCheckbox.hasValue() && isAllSelected()" + [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()"> + </mat-checkbox> + </mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="70px"> + <mat-checkbox (click)="$event.stopPropagation()" + (change)="$event ? tableselectionCheckbox.toggle(row) : null" + [checked]="tableselectionCheckbox.isSelected(row)"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="groupname"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxFlex="300px">Login-Gruppe</mat-header-cell> + <mat-cell *matCellDef="let element" fxFlex="300px">{{element.groupname}}</mat-cell> + </ng-container> + + <ng-container matColumnDef="bookletsStarted"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Testhefte gestartet</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{element.bookletsStarted}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="num_units_min"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Aufgaben min</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_min}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="num_units_max"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Aufgaben max</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_max}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="num_units_mean"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Aufgaben Mittelwert</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_mean | number:'1.1-1'}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="lastchange"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Letzte Änderung</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.lastchange | date:'dd.MM.yyyy HH:mm'}} </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> +</div> diff --git a/src/app/workspace/results/results.component.spec.ts b/src/app/workspace/results/results.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..23c08dfbd9069ff0c88a396817f452a838b0fd51 --- /dev/null +++ b/src/app/workspace/results/results.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResultsComponent } from './results.component'; + +describe('SharedFilesComponent', () => { + let component: ResultsComponent; + let fixture: ComponentFixture<ResultsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ResultsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResultsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/workspace/results/results.component.ts b/src/app/workspace/results/results.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c4bfe54e7e65b7c3b2871b77ff782fbf0016f06 --- /dev/null +++ b/src/app/workspace/results/results.component.ts @@ -0,0 +1,273 @@ +import { LogData } from '../workspace.interfaces'; +import { WorkspaceDataService } from '../workspacedata.service'; +import { ConfirmDialogComponent, ConfirmDialogData } from 'iqb-components'; +import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; +import { BackendService } from '../backend.service'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { SelectionModel } from '@angular/cdk/collections'; +import { saveAs } from 'file-saver'; +import { ResultData, UnitResponse, ReviewData } from '../workspace.interfaces'; +import { Subscription } from 'rxjs'; + + +@Component({ + templateUrl: './results.component.html', + styleUrls: ['./results.component.css'] +}) +export class ResultsComponent implements OnInit, OnDestroy { + displayedColumns: string[] = ['selectCheckbox', 'groupname', 'bookletsStarted', 'num_units_min', 'num_units_max', 'num_units_mean', 'lastchange']; + public resultDataSource = new MatTableDataSource<ResultData>([]); + // prepared for selection if needed sometime + public tableselectionCheckbox = new SelectionModel<ResultData>(true, []); + public dataLoading = false; + private workspaceIdSubscription: Subscription = null; + + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + private bs: BackendService, + public wds: WorkspaceDataService, + private deleteConfirmDialog: MatDialog, + public snackBar: MatSnackBar + ) { } + + ngOnInit() { + this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(() => { + this.updateTable(); + }); + } + + updateTable() { + this.tableselectionCheckbox.clear(); + if (this.wds.wsRole === 'MO') { + this.resultDataSource = new MatTableDataSource<ResultData>([]); + } else { + this.dataLoading = true; + this.bs.getResultData().subscribe( + (resultData: ResultData[]) => { + this.dataLoading = false; + this.resultDataSource = new MatTableDataSource<ResultData>(resultData); + this.resultDataSource.sort = this.sort; + } + ); + } + } + + isAllSelected() { + const numSelected = this.tableselectionCheckbox.selected.length; + const numRows = this.resultDataSource.data.length; + return numSelected === numRows; + } + + masterToggle() { + this.isAllSelected() ? + this.tableselectionCheckbox.clear() : + this.resultDataSource.data.forEach(row => this.tableselectionCheckbox.select(row)); + } + + // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + downloadResponsesCSV() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + this.bs.getResponses(selectedGroups).subscribe( + (responseData: UnitResponse[]) => { + if (responseData.length > 0) { + const columnDelimiter = ';'; + const lineDelimiter = '\n'; + let myCsvData = 'groupname' + columnDelimiter + + 'loginname' + columnDelimiter + + 'code' + columnDelimiter + + 'bookletname' + columnDelimiter + + 'unitname' + columnDelimiter + + 'responses' + columnDelimiter + + 'restorePoint' + columnDelimiter + + 'responseType' + columnDelimiter + + 'response-ts' + columnDelimiter + + 'restorePoint-ts' + columnDelimiter + + 'laststate' + lineDelimiter; + responseData.forEach((resp: UnitResponse) => { + myCsvData += '"' + resp.groupname + '"' + columnDelimiter + + '"' + resp.loginname + '"' + columnDelimiter + + '"' + resp.code + '"' + columnDelimiter + + '"' + resp.bookletname + '"' + columnDelimiter + + '"' + resp.unitname + '"' + columnDelimiter; + if ((resp.responses !== null) && (resp.responses.length > 0)) { + myCsvData += resp.responses.replace(/\\"/g, '""') + columnDelimiter; + } else { + myCsvData += columnDelimiter; + } + if ((resp.restorepoint !== null) && (resp.restorepoint.length > 0)) { + myCsvData += resp.restorepoint.replace(/\\"/g, '""') + columnDelimiter; + } else { + myCsvData += columnDelimiter; + } + if ((resp.responsetype !== null) && (resp.responsetype.length > 0)) { + myCsvData += '"' + resp.responsetype + '"' + columnDelimiter; + } else { + myCsvData += columnDelimiter; + } + myCsvData += resp.responses_ts + columnDelimiter + resp.restorepoint_ts + columnDelimiter; + if ((resp.laststate !== null) && (resp.laststate.length > 0)) { + myCsvData += '"' + resp.laststate + '"' + lineDelimiter; + } else { + myCsvData += lineDelimiter; + } + }); + const blob = new Blob([myCsvData], {type: 'text/csv;charset=utf-8'}); + saveAs(blob, 'iqb-testcenter-responses.csv'); + } else { + this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000}); + } + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); + } + } + + // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + downloadReviewsCSV() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + this.bs.getReviews(selectedGroups).subscribe( + (responseData: ReviewData[]) => { + if (responseData.length > 0) { + // collect categories + const allCategories: string[] = []; + responseData.forEach((resp: ReviewData) => { + resp.categories.split(' ').forEach(s => { + const s_trimmed = s.trim(); + if (s_trimmed.length > 0) { + if (!allCategories.includes(s_trimmed)) { + allCategories.push(s_trimmed); + } + } + }); + }); + + const columnDelimiter = ';'; + const lineDelimiter = '\n'; + let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + + 'bookletname' + columnDelimiter + 'unitname' + columnDelimiter + + 'priority' + columnDelimiter; + allCategories.forEach(s => { + myCsvData += 'category: ' + s + columnDelimiter; + }); + myCsvData += 'reviewtime' + columnDelimiter + 'entry' + lineDelimiter; + + responseData.forEach((resp: ReviewData) => { + if ((resp.entry !== null) && (resp.entry.length > 0)) { + myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + + columnDelimiter + '"' + resp.code + '"' + columnDelimiter + '"' + resp.bookletname + '"' + + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter + '"' + + resp.priority + '"' + columnDelimiter; + const resp_categories = resp.categories.split(' '); + allCategories.forEach(s => { + if (resp_categories.includes(s)) { + myCsvData += '"X"' + columnDelimiter; + } else { + myCsvData += columnDelimiter; + } + }); + myCsvData += '"' + resp.reviewtime + '"' + columnDelimiter + '"' + resp.entry + '"' + lineDelimiter; + } + }); + const blob = new Blob([myCsvData], {type: 'text/csv;charset=utf-8'}); + saveAs(blob, 'iqb-testcenter-reviews.csv'); + } else { + this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000}); + } + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); + } + } + + // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + downloadLogsCSV() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + this.bs.getLogs(selectedGroups).subscribe( + (responseData: LogData[]) => { + if (responseData.length > 0) { + const columnDelimiter = ';'; + const lineDelimiter = '\n'; + let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter + + 'bookletname' + columnDelimiter + 'unitname' + columnDelimiter + + 'timestamp' + columnDelimiter + 'logentry' + lineDelimiter; + responseData.forEach((resp: LogData) => { + if ((resp.logentry !== null) && (resp.logentry.length > 0)) { + myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + columnDelimiter + '"' + resp.code + '"' + columnDelimiter + + '"' + resp.bookletname + '"' + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter + '"' + + resp.timestamp.toString() + '"' + columnDelimiter + resp.logentry.replace(/\\"/g, '""') + lineDelimiter; + } + }); + const blob = new Blob([myCsvData], {type: 'text/csv;charset=utf-8'}); + saveAs(blob, 'iqb-testcenter-logs.csv'); + } else { + this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000}); + } + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); + } + } + + deleteData() { + if (this.tableselectionCheckbox.selected.length > 0) { + const selectedGroups: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedGroups.push(element.groupname); + }); + + let prompt = 'Es werden alle Antwort- und Logdaten in der Datenbank für diese '; + if (selectedGroups.length > 1) { + prompt = prompt + selectedGroups.length + ' Gruppen '; + } else { + prompt = prompt + ' Gruppe "' + selectedGroups[0] + '" '; + } + + const dialogRef = this.deleteConfirmDialog.open(ConfirmDialogComponent, { + width: '400px', + data: <ConfirmDialogData>{ + title: 'Löschen von Gruppendaten', + content: prompt + 'gelöscht. Fortsetzen?', + confirmbuttonlabel: 'Gruppendaten löschen', + showcancel: true + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result !== false) { + // ========================================================= + this.dataLoading = true; + this.bs.deleteData(selectedGroups).subscribe(() => { + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); + } + }); + } + } + + // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + ngOnDestroy() { + if (this.workspaceIdSubscription !== null) { + this.workspaceIdSubscription.unsubscribe(); + } + } +} diff --git a/src/app/workspace/syscheck/syscheck.component.css b/src/app/workspace/syscheck/syscheck.component.css new file mode 100644 index 0000000000000000000000000000000000000000..c495430b14a19cf4d0fe394fb8a7217058eff209 --- /dev/null +++ b/src/app/workspace/syscheck/syscheck.component.css @@ -0,0 +1,3 @@ +.mat-icon { + margin-right: 5px; +} diff --git a/src/app/workspace/syscheck/syscheck.component.html b/src/app/workspace/syscheck/syscheck.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d059389ba3aa33478aaa029a72dd38111ad5f8e2 --- /dev/null +++ b/src/app/workspace/syscheck/syscheck.component.html @@ -0,0 +1,57 @@ +<div class="columnhost" fxLayout="column"> + <div class="spinner-container" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + <div fxLayout="row" fxLayoutGap="10px"> + <button mat-raised-button (click)="downloadReportsCSV()" [disabled]="!tableselectionCheckbox.hasValue()" + matTooltip="Download Berichte als CSV für Excel" matTooltipPosition="above"> + <mat-icon>cloud_download</mat-icon>Berichte + </button> + <button mat-raised-button (click)="deleteReports()" [disabled]="!tableselectionCheckbox.hasValue()" + matTooltip="Löschen Berichte für markierte System-Checks" matTooltipPosition="above"> + <mat-icon>delete</mat-icon> + </button> + </div> + + <mat-table [dataSource]="resultDataSource" matSort> + <ng-container matColumnDef="selectCheckbox"> + <mat-header-cell *matHeaderCellDef fxFlex="70px"> + <mat-checkbox (change)="$event ? masterToggle() : null" + [checked]="tableselectionCheckbox.hasValue() && isAllSelected()" + [indeterminate]="tableselectionCheckbox.hasValue() && !isAllSelected()"> + </mat-checkbox> + </mat-header-cell> + <mat-cell *matCellDef="let row" fxFlex="70px"> + <mat-checkbox (click)="$event.stopPropagation()" + (change)="$event ? tableselectionCheckbox.toggle(row) : null" + [checked]="tableselectionCheckbox.isSelected(row)"> + </mat-checkbox> + </mat-cell> + </ng-container> + + <ng-container matColumnDef="syscheckId"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxFlex="300px">System-Check Id</mat-header-cell> + <mat-cell *matCellDef="let element" fxFlex="300px">{{element.id}}</mat-cell> + </ng-container> + + <ng-container matColumnDef="syscheckLabel"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="start center">System-Check Name</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="start center"> {{element.label}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="number"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Anzahl Berichte</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.count}} </mat-cell> + </ng-container> + + <ng-container matColumnDef="details"> + <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Betriebssysteme und Browser</mat-header-cell> + <mat-cell *matCellDef="let element" fxLayout="column" fxLayoutAlign="center start"> + <div *ngFor="let d of element.details">{{d}}</div> + </mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> + </mat-table> +</div> diff --git a/src/app/workspace/syscheck/syscheck.component.spec.ts b/src/app/workspace/syscheck/syscheck.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1210159f5a75b9fd205fab50fdd402ff2e2be8f --- /dev/null +++ b/src/app/workspace/syscheck/syscheck.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SyscheckComponent } from './syscheck.component'; + +describe('SyscheckComponent', () => { + let component: SyscheckComponent; + let fixture: ComponentFixture<SyscheckComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SyscheckComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SyscheckComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/workspace/syscheck/syscheck.component.ts b/src/app/workspace/syscheck/syscheck.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2cf19b77d82eaf298fa0d7bb12008801ad6c88d5 --- /dev/null +++ b/src/app/workspace/syscheck/syscheck.component.ts @@ -0,0 +1,124 @@ +import { ConfirmDialogComponent, ConfirmDialogData } from 'iqb-components'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { BackendService } from '../backend.service'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { SelectionModel } from '@angular/cdk/collections'; +import { saveAs } from 'file-saver'; +import { SysCheckStatistics } from '../workspace.interfaces'; + + +@Component({ + templateUrl: './syscheck.component.html', + styleUrls: ['./syscheck.component.css'] +}) +export class SyscheckComponent implements OnInit { + displayedColumns: string[] = ['selectCheckbox', 'syscheckLabel', 'number', 'details']; + public resultDataSource = new MatTableDataSource<SysCheckStatistics>([]); + // prepared for selection if needed sometime + public tableselectionCheckbox = new SelectionModel<SysCheckStatistics>(true, []); + public dataLoading = false; + + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + private bs: BackendService, + private deleteConfirmDialog: MatDialog, + public snackBar: MatSnackBar + ) { + } + + ngOnInit() { + this.updateTable(); + } + + updateTable() { + this.dataLoading = true; + this.tableselectionCheckbox.clear(); + this.bs.getSysCheckReportList().subscribe( + (resultData: SysCheckStatistics[]) => { + this.dataLoading = false; + this.resultDataSource = new MatTableDataSource<SysCheckStatistics>(resultData); + this.resultDataSource.sort = this.sort; + } + ); + } + + isAllSelected() { + const numSelected = this.tableselectionCheckbox.selected.length; + const numRows = this.resultDataSource.data.length; + return numSelected === numRows; + } + + masterToggle() { + this.isAllSelected() ? + this.tableselectionCheckbox.clear() : + this.resultDataSource.data.forEach(row => this.tableselectionCheckbox.select(row)); + } + + // 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + downloadReportsCSV() { + if (this.tableselectionCheckbox.selected.length > 0) { + this.dataLoading = true; + const selectedReports: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedReports.push(element.id); + }); + this.bs.getSysCheckReport(selectedReports, ';', '"').subscribe( + (reportData: string[]) => { + if (reportData.length > 0) { + const lineDelimiter = '\n'; + let myCsvData = ''; + reportData.forEach((repLine: string) => { + myCsvData += repLine + lineDelimiter; + }); + const blob = new Blob([myCsvData], {type: 'text/csv;charset=utf-8'}); + saveAs(blob, 'iqb-testcenter-syscheckreports.csv'); + } else { + this.snackBar.open('Keine Daten verfügbar.', 'Fehler', {duration: 3000}); + } + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); + } + } + + deleteReports() { + if (this.tableselectionCheckbox.selected.length > 0) { + const selectedReports: string[] = []; + this.tableselectionCheckbox.selected.forEach(element => { + selectedReports.push(element.id); + }); + + let prompt = 'Es werden alle Berichte für diese'; + if (selectedReports.length > 1) { + prompt = prompt + ' ' + selectedReports.length + ' System-Checks '; + } else { + prompt = prompt + 'n System-Check "' + selectedReports[0] + '" '; + } + + const dialogRef = this.deleteConfirmDialog.open(ConfirmDialogComponent, { + width: '400px', + data: <ConfirmDialogData>{ + title: 'Löschen von Berichten', + content: prompt + 'gelöscht. Fortsetzen?', + confirmbuttonlabel: 'Berichtsdaten löschen', + showcancel: true + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result !== false) { + // ========================================================= + this.dataLoading = true; + this.bs.deleteSysCheckReports(selectedReports).subscribe(() => { + this.tableselectionCheckbox.clear(); + this.dataLoading = false; + }); + } + }); + } + } +} diff --git a/src/app/workspace/workspace-routing.module.ts b/src/app/workspace/workspace-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fefdd10e0c30cd3ba74e520778fcdd653ae6214 --- /dev/null +++ b/src/app/workspace/workspace-routing.module.ts @@ -0,0 +1,28 @@ +import { SyscheckComponent } from './syscheck/syscheck.component'; +import { MonitorComponent } from './monitor/monitor.component'; +import { ResultsComponent } from './results/results.component'; +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { FilesComponent } from './files/files.component'; +import { WorkspaceComponent } from './workspace.component'; + +const routes: Routes = [ + { + path: 'ws/:ws', + component: WorkspaceComponent, + children: [ + {path: '', redirectTo: 'monitor', pathMatch: 'full'}, + {path: 'files', component: FilesComponent}, + {path: 'syscheck', component: SyscheckComponent}, + {path: 'monitor', component: MonitorComponent}, + {path: 'results', component: ResultsComponent}, + {path: '**', component: MonitorComponent} + ] + }]; + + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class WorkspaceRoutingModule { } diff --git a/src/app/workspace/workspace.component.css b/src/app/workspace/workspace.component.css new file mode 100644 index 0000000000000000000000000000000000000000..b6162710eeaea472d7054f155f74e142018bdcfd --- /dev/null +++ b/src/app/workspace/workspace.component.css @@ -0,0 +1,35 @@ +/* --------------------------------------------- */ +#buttonsContainer { + color: white; + padding: 0 10px 0 0; +} +#buttonsContainer .material-icons { + font-size: 2.0rem; +} +#buttonsContainer img { + width: 100px; +} + +/* --------------------------------------------- */ +mat-toolbar { + position: fixed; + z-index: 100; + top: 4px; + right: 90px; +} + +#buttonsContainer .material-icons { + position: relative; + top: -8px; + font-size: 36px; + padding: 2px; +} + +.adminbackground { + flex: 10 0 900px; + box-shadow: 5px 10px 20px black; + background-color: white; + min-height: 85%; + margin: 15px; + padding: 25px; +} diff --git a/src/app/workspace/workspace.component.html b/src/app/workspace/workspace.component.html new file mode 100644 index 0000000000000000000000000000000000000000..6f5cf00c4d26e4735c49e8b90d0cc25c25d8e7df --- /dev/null +++ b/src/app/workspace/workspace.component.html @@ -0,0 +1,27 @@ +<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="start center"> + <a [routerLink]="['/']"> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + </a> + <div fxLayout="row wrap" fxLayoutAlign="space-between center" fxFlex> + <div class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</div> + <div>IQB-Testcenter Verwaltung</div> + <div>{{ wds.wsName }} ({{ wds.wsRole }})</div> + </div> +</div> +<div class="page-body"> + <div class="adminbackground"> + + + <nav mat-tab-nav-bar> + <a mat-tab-link + *ngFor="let link of wds.navLinks" + [routerLink]="link.path" + routerLinkActive #rla="routerLinkActive" + [active]="rla.isActive"> + {{link.label}} + </a> + </nav> + + <router-outlet></router-outlet> + </div> +</div> diff --git a/src/app/workspace/workspace.component.spec.ts b/src/app/workspace/workspace.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ea31a8f29e3256b71a7d1772483fae203751971 --- /dev/null +++ b/src/app/workspace/workspace.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkspaceComponent } from './workspace.component'; + +describe('WorkspaceComponent', () => { + let component: WorkspaceComponent; + let fixture: ComponentFixture<WorkspaceComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkspaceComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkspaceComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/workspace/workspace.component.ts b/src/app/workspace/workspace.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..1b7ed70ce1e0312bc8581cceeefd5fee35e2f799 --- /dev/null +++ b/src/app/workspace/workspace.component.ts @@ -0,0 +1,42 @@ +import { WorkspaceDataService } from './workspacedata.service'; +import { MainDataService } from '../maindata.service'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { Component, OnInit, OnDestroy } from '@angular/core'; + + +@Component({ + templateUrl: './workspace.component.html', + styleUrls: ['./workspace.component.css'] +}) +export class WorkspaceComponent implements OnInit, OnDestroy { + private routingSubscription: Subscription = null; + private logindataSubscription: Subscription = null; + + constructor( + private route: ActivatedRoute, + public mds: MainDataService, + public wds: WorkspaceDataService + ) { } + + ngOnInit() { + this.routingSubscription = this.route.params.subscribe(params => { + const ws = Number(params['ws']); + this.wds.setWorkspace(ws, this.mds.getWorkspaceRole(ws), this.mds.getWorkspaceName(ws)); + }); + + this.logindataSubscription = this.mds.loginData$.subscribe(() => { + this.wds.setWorkspace(this.wds.ws, this.mds.getWorkspaceRole(this.wds.ws), this.mds.getWorkspaceName(this.wds.ws)); + }); + } + + + ngOnDestroy() { + if (this.routingSubscription !== null) { + this.routingSubscription.unsubscribe(); + } + if (this.logindataSubscription !== null) { + this.logindataSubscription.unsubscribe(); + } + } +} diff --git a/src/app/workspace/workspace.interceptor.ts b/src/app/workspace/workspace.interceptor.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa6ed54646e8f7fbba0f96a004289b2d89bdb77a --- /dev/null +++ b/src/app/workspace/workspace.interceptor.ts @@ -0,0 +1,29 @@ +import { WorkspaceDataService } from './workspacedata.service'; +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() + +export class WorkspaceInterceptor implements HttpInterceptor { + constructor(public wds: WorkspaceDataService) {} + + intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + const ws = this.wds.workspaceId$.getValue(); + if (ws >= 0) { + const authDataStr = request.headers.get('AuthToken'); + let authData = {}; + if (authDataStr) { + authData = JSON.parse(authDataStr); + } + authData['ws'] = ws; + return next.handle(request.clone({ + setHeaders: { + AuthToken: JSON.stringify(authData) + } + })); + } else { + return next.handle(request); + } + } +} diff --git a/src/app/workspace/workspace.interfaces.ts b/src/app/workspace/workspace.interfaces.ts new file mode 100644 index 0000000000000000000000000000000000000000..b53f17d1622617ce9b17a3e4f533e1666f7ee912 --- /dev/null +++ b/src/app/workspace/workspace.interfaces.ts @@ -0,0 +1,96 @@ +export interface GetFileResponseData { + filename: string; + filesize: number; + filesizestr: string; + filedatetime: string; + filedatetimestr: string; + type: string; + typelabel: string; + isChecked: boolean; +} + +export interface CheckWorkspaceResponseData { + errors: string[]; + infos: string[]; + warnings: string[]; +} + + +export interface GroupResponse { + name: string; + testsTotal: number; + testsStarted: number; + responsesGiven: number; +} + +export interface BookletsStarted { + groupname: string; + loginname: string; + code: string; + bookletname: string; + locked: boolean; + laststart: Date; +} + +export interface UnitResponse { + groupname: string; + loginname: string; + code: string; + bookletname: string; + unitname: string; + responses: string; + restorepoint: string; + responsetype: string; + responses_ts: number; + restorepoint_ts: number; + laststate: string; +} + +export interface MonitorData { + groupname: string; + loginsPrepared: number; + personsPrepared: number; + bookletsPrepared: number; + bookletsStarted: number; + bookletsLocked: number; + laststart: Date; + laststartStr: string; +} + +export interface ResultData { + groupname: string; + bookletsStarted: number; + num_units_min: number; + num_units_max: number; + num_units_mean: number; + lastchange: number; +} + +export interface LogData { + groupname: string; + loginname: string; + code: string; + bookletname: string; + unitname: string; + timestamp: number; + logentry: string; +} + +export interface ReviewData { + groupname: string; + loginname: string; + code: string; + bookletname: string; + unitname: string; + priority: number; + categories: string; + reviewtime: Date; + entry: string; +} + +export interface SysCheckStatistics { + id: string; + label: string; + count: number; + details: string[]; +} diff --git a/src/app/workspace/workspace.module.ts b/src/app/workspace/workspace.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..ead279306643a60d0285c5dca1baa6eaab3a7680 --- /dev/null +++ b/src/app/workspace/workspace.module.ts @@ -0,0 +1,88 @@ +import { FlexLayoutModule } from '@angular/flex-layout'; +import { BackendService } from './backend.service'; +import { IqbFilesModule } from '../iqb-files'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NgModule} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { WorkspaceDataService } from './workspacedata.service'; + +import { WorkspaceRoutingModule } from './workspace-routing.module'; +import { WorkspaceComponent } from './workspace.component'; +import { FilesComponent } from './files/files.component'; +import { ResultsComponent } from './results/results.component'; + +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MonitorComponent } from './monitor/monitor.component'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { SyscheckComponent } from './syscheck/syscheck.component'; +import { IqbComponentsModule } from 'iqb-components'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { WorkspaceInterceptor } from './workspace.interceptor'; + +@NgModule({ + imports: [ + IqbFilesModule, + CommonModule, + WorkspaceRoutingModule, + MatTableModule, + MatTabsModule, + MatIconModule, + MatSelectModule, + MatCheckboxModule, + MatSortModule, + MatCardModule, + MatExpansionModule, + ReactiveFormsModule, + MatProgressSpinnerModule, + MatDialogModule, + MatButtonModule, + MatTooltipModule, + MatFormFieldModule, + MatInputModule, + MatToolbarModule, + MatSnackBarModule, + MatGridListModule, + IqbComponentsModule, + FlexLayoutModule, + MatCardModule, + FlexLayoutModule + ], + exports: [ + WorkspaceComponent + ], + declarations: [ + WorkspaceComponent, + FilesComponent, + ResultsComponent, + MonitorComponent, + SyscheckComponent + ], + providers: [ + // interceptor adds ws to AuthToken + // not working when module is lazy loaded! + { + provide: HTTP_INTERCEPTORS, + useClass: WorkspaceInterceptor, + multi: true + }, + BackendService, + WorkspaceDataService + ], +}) + +export class WorkspaceModule { } diff --git a/src/app/workspace/workspacedata.service.spec.ts b/src/app/workspace/workspacedata.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4efb2cb6ef585b014ba53d398f1defc114ef4ac8 --- /dev/null +++ b/src/app/workspace/workspacedata.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { WorkspaceDataService } from './workspacedata.service'; + +describe('WorkspaceDataService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [WorkspaceDataService] + }); + }); + + it('should be created', inject([WorkspaceDataService], (service: WorkspaceDataService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/workspace/workspacedata.service.ts b/src/app/workspace/workspacedata.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..07fc24f00f71db2011b2a7a8bc50cc343dfd82b2 --- /dev/null +++ b/src/app/workspace/workspacedata.service.ts @@ -0,0 +1,71 @@ +import { BehaviorSubject } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { ServerError } from 'iqb-components'; + +@Injectable({ + providedIn: 'root' +}) + +@Injectable() +export class WorkspaceDataService { + public workspaceId$ = new BehaviorSubject<number>(-1); + public globalErrorMsg$ = new BehaviorSubject<ServerError>(null); + + public get ws(): number { + return this.workspaceId$.getValue(); + } + private _wsRole = ''; + public get wsRole(): string { + return this._wsRole; + } + private _wsName = ''; + public get wsName(): string { + return this._wsName; + } + public navLinks = []; + + + private navLinksRW = [ + {path: 'files', label: 'Dateien'}, + {path: 'syscheck', label: 'System-Check Berichte'}, + {path: 'monitor', label: 'Monitor'}, + {path: 'results', label: 'Ergebnisse'} + ]; + private navLinksRO = [ + {path: 'files', label: 'Dateien'}, + {path: 'syscheck', label: 'System-Check Berichte'}, + {path: 'monitor', label: 'Monitor'}, + {path: 'results', label: 'Ergebnisse'} + ]; + private navLinksMO = [ + {path: 'monitor', label: 'Monitor'} + ]; + + setNewErrorMsg(err: ServerError = null) { + this.globalErrorMsg$.next(err); + } + + setWorkspace(newId: number, newRole: string, newName: string) { + this._wsName = newName; + this._wsRole = newRole; + switch (newRole.toUpperCase()) { + case 'RW': { + this.navLinks = this.navLinksRW; + break; + } + case 'RO': { + this.navLinks = this.navLinksRO; + break; + } + case 'MO': { + this.navLinks = this.navLinksMO; + break; + } + default: { + this.navLinks = []; + break; + } + } + this.workspaceId$.next(newId); + } +} diff --git a/src/assets/browserslist b/src/assets/browserslist new file mode 100644 index 0000000000000000000000000000000000000000..8e09ab492e26c2b2c0920a75dd795811f5815369 --- /dev/null +++ b/src/assets/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/src/includes/FileSaver.js b/src/includes/FileSaver.js new file mode 100644 index 0000000000000000000000000000000000000000..fb7149425dd186c380d618a99c1ccdffd9de409b --- /dev/null +++ b/src/includes/FileSaver.js @@ -0,0 +1,188 @@ +/* FileSaver.js + * A saveAs() FileSaver implementation. + * 1.3.2 + * 2016-06-16 18:25:19 + * + * By Eli Grey, http://eligrey.com + * License: MIT + * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md + */ + +/*global self */ +/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ + +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + +var saveAs = saveAs || (function(view) { + "use strict"; + // IE <10 is explicitly unsupported + if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + return; + } + var + doc = view.document + // only get URL when necessary in case Blob.js hasn't overridden it yet + , get_URL = function() { + return view.URL || view.webkitURL || view; + } + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") + , can_use_save_link = "download" in save_link + , click = function(node) { + var event = new MouseEvent("click"); + node.dispatchEvent(event); + } + , is_safari = /constructor/i.test(view.HTMLElement) || view.safari + , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) + , throw_outside = function(ex) { + (view.setImmediate || view.setTimeout)(function() { + throw ex; + }, 0); + } + , force_saveable_type = "application/octet-stream" + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to + , arbitrary_revoke_timeout = 1000 * 40 // in ms + , revoke = function(file) { + var revoker = function() { + if (typeof file === "string") { // file is an object URL + get_URL().revokeObjectURL(file); + } else { // file is a File + file.remove(); + } + }; + setTimeout(revoker, arbitrary_revoke_timeout); + } + , dispatch = function(filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver["on" + event_types[i]]; + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + } + , auto_bom = function(blob) { + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); + } + return blob; + } + , FileSaver = function(blob, name, no_auto_bom) { + if (!no_auto_bom) { + blob = auto_bom(blob); + } + // First try a.download, then web filesystem, then object URLs + var + filesaver = this + , type = blob.type + , force = type === force_saveable_type + , object_url + , dispatch_all = function() { + dispatch(filesaver, "writestart progress write writeend".split(" ")); + } + // on any filesys errors revert to saving with object URLs + , fs_error = function() { + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { + // Safari doesn't allow downloading of blob urls + var reader = new FileReader(); + reader.onloadend = function() { + var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + var popup = view.open(url, '_blank'); + if(!popup) view.location.href = url; + url=undefined; // release reference before dispatching + filesaver.readyState = filesaver.DONE; + dispatch_all(); + }; + reader.readAsDataURL(blob); + filesaver.readyState = filesaver.INIT; + return; + } + // don't create more object URLs than needed + if (!object_url) { + object_url = get_URL().createObjectURL(blob); + } + if (force) { + view.location.href = object_url; + } else { + var opened = view.open(object_url, "_blank"); + if (!opened) { + // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html + view.location.href = object_url; + } + } + filesaver.readyState = filesaver.DONE; + dispatch_all(); + revoke(object_url); + } + ; + filesaver.readyState = filesaver.INIT; + + if (can_use_save_link) { + object_url = get_URL().createObjectURL(blob); + setTimeout(function() { + save_link.href = object_url; + save_link.download = name; + click(save_link); + dispatch_all(); + revoke(object_url); + filesaver.readyState = filesaver.DONE; + }); + return; + } + + fs_error(); + } + , FS_proto = FileSaver.prototype + , saveAs = function(blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || "download", no_auto_bom); + } + ; + // IE 10+ (native saveAs) + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { + return function(blob, name, no_auto_bom) { + name = name || blob.name || "download"; + + if (!no_auto_bom) { + blob = auto_bom(blob); + } + return navigator.msSaveOrOpenBlob(blob, name); + }; + } + + FS_proto.abort = function(){}; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; + + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; + + return saveAs; +}( + typeof self !== "undefined" && self + || typeof window !== "undefined" && window + || this.content +)); +// `self` is undefined in Firefox for Android content script context +// while `this` is nsIContentFrameMessageManager +// with an attribute `content` that corresponds to the window + +if (typeof module !== "undefined" && module.exports) { + module.exports.saveAs = saveAs; +} else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { + define("FileSaver.js", function() { + return saveAs; + }); +} diff --git a/src/index.html.ADMIN b/src/index.html.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..c6474ea70185a8d81cb39c03bb2fdadd7ed24546 --- /dev/null +++ b/src/index.html.ADMIN @@ -0,0 +1,25 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>IQB-Testcenter Verwaltung</title> + <base href="/"> + + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png?v=Hujjik765gt"> + <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png?v=Hujjik765gt"> + <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png?v=Hujjik765gt"> + <link rel="manifest" href="/assets/site.webmanifest?v=Hujjik765gt"> + <link rel="mask-icon" href="/assets/safari-pinned-tab.svg?v=Hujjik765gt" color="#5bbad5"> + <link rel="shortcut icon" href="/assets/favicon.ico?v=Hujjik765gt"> + <meta name="msapplication-TileColor" content="#2d89ef"> + <meta name="theme-color" content="#ffffff"> + + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin" rel="stylesheet"> + +</head> +<body> + <app-root></app-root> +</body> +</html> diff --git a/src/index.html b/src/index.html.TC similarity index 100% rename from src/index.html rename to src/index.html.TC diff --git a/src/iqb-theme2.scss b/src/iqb-theme2.scss new file mode 100644 index 0000000000000000000000000000000000000000..215f8b27c77f1f2d867bd7be5cd7a5ddf44db16b --- /dev/null +++ b/src/iqb-theme2.scss @@ -0,0 +1,16 @@ +@import '~@angular/material/theming'; +@include mat-core(); + +$iqb-primary: mat-palette($mat-cyan, 900); +$iqb-accent: mat-palette($mat-light-green, A200); +$iqb-app-theme: mat-light-theme($iqb-primary, $iqb-accent); +@include angular-material-theme($iqb-app-theme); + +body { + overflow: hidden; + width: 100%; + height: 100%; + margin: 0; + font-family: "Orienta"; + background: linear-gradient(to left, #003333, #045659, #0d7b84, #1aa2b2, #2acae5) +} diff --git a/src/polyfills.ts b/src/polyfills.ts index 303ce7eb2bc7d19bc0670cc2e792fe5f67b56ece..32c599fd7e035be226e3c8dde2b019da139e6444 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -19,6 +19,7 @@ */ /** IE9, IE10 and IE11 requires all of the following polyfills. **/ + import 'core-js/es6/symbol'; import 'core-js/es6/object'; import 'core-js/es6/function'; @@ -42,6 +43,7 @@ import 'core-js/es6/reflect'; // npm install git+https://github.com/jugglinmike/srcdoc-polyfill // import 'srcdoc-polyfill'; + /** Evergreen browsers require these. **/ // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. import 'core-js/es7/reflect'; @@ -52,23 +54,29 @@ import 'core-js/es7/reflect'; * 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 */ + (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + /* * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge */ + (window as any).__Zone_enable_cross_context_check = true; + /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ @@ -79,3 +87,5 @@ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ + +import 'hammerjs'; diff --git a/src/styles.css.ADMIN b/src/styles.css.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..a7578e00f336c912d31f0ff12604b768e84648b7 --- /dev/null +++ b/src/styles.css.ADMIN @@ -0,0 +1,39 @@ +/* orienta-regular - latin */ +@font-face { + font-family: 'Orienta'; + font-style: normal; + font-weight: 400; + src: local('Orienta'), local('Orienta-Regular'), + url('assets/orienta-v5-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('assets/orienta-v5-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +.page-body { + overflow-x: auto; + position: absolute; + width: 100%; + top: 60px; + bottom: 0; +} + + +.spinner-container { + position: fixed; + z-index: 999; + height: 2em; + width: 2em; + overflow: show; + margin: auto; + top: 0; + left: 0; + bottom: 0; + right: 0; +} + +.logo img { + width: 100px; +} + +.error-msg { + color: brown; +} diff --git a/src/styles.css b/src/styles.css.TC similarity index 100% rename from src/styles.css rename to src/styles.css.TC diff --git a/src/tsconfig.app.json.ADMIN b/src/tsconfig.app.json.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..8ea061ea1b88d9c0fa2e96ecc87dcfecbd182e80 --- /dev/null +++ b/src/tsconfig.app.json.ADMIN @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": [] + }, + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json.TC similarity index 100% rename from src/tsconfig.app.json rename to src/tsconfig.app.json.TC diff --git a/src/tsconfig.spec.json.ADMIN b/src/tsconfig.spec.json.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..de7733630eb224b246854a20934f792051926e8f --- /dev/null +++ b/src/tsconfig.spec.json.ADMIN @@ -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/src/tsconfig.spec.json b/src/tsconfig.spec.json.TC similarity index 100% rename from src/tsconfig.spec.json rename to src/tsconfig.spec.json.TC diff --git a/src/tslint.json.ADMIN b/src/tslint.json.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..52e2c1a5a74ce268bec92d34cd3bca751624adb3 --- /dev/null +++ b/src/tslint.json.ADMIN @@ -0,0 +1,17 @@ +{ + "extends": "../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/src/tslint.json b/src/tslint.json.TC similarity index 100% rename from src/tslint.json rename to src/tslint.json.TC diff --git a/testcenter-iqb-ng.iml b/testcenter-iqb-ng.iml new file mode 100644 index 0000000000000000000000000000000000000000..da8860ce3875d8d8e4c38f7707a847e8a2fd4a4f --- /dev/null +++ b/testcenter-iqb-ng.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="WEB_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/dist" /> + <excludeFolder url="file://$MODULE_DIR$/tmp" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/tsconfig.json.ADMIN b/tsconfig.json.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..d47546887c691397afbe24aea3e0d2fbd5ccefc1 --- /dev/null +++ b/tsconfig.json.ADMIN @@ -0,0 +1,31 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "downlevelIteration": true, + "importHelpers": true, + "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" + ], + "paths": { + "core-js/es7/reflect": [ + "node_modules/core-js/proposals/reflect-metadata" + ], + "core-js/es6/reflect": [ + "node_modules/core-js/proposals/reflect-metadata" + ] + } + } +} diff --git a/tsconfig.json b/tsconfig.json.TC similarity index 100% rename from tsconfig.json rename to tsconfig.json.TC diff --git a/tslint.json.ADMIN b/tslint.json.ADMIN new file mode 100644 index 0000000000000000000000000000000000000000..2da62c3d7286e9004679bfa18f9df48cb40471be --- /dev/null +++ b/tslint.json.ADMIN @@ -0,0 +1,135 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer", + "node_modules/tslint" + ], + "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, + "rxjs-collapse-imports": true, + "rxjs-pipeable-operators-only": true, + "rxjs-no-static-observable-methods": true, + "rxjs-proper-imports": 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 + } +} diff --git a/tslint.json b/tslint.json.TC similarity index 100% rename from tslint.json rename to tslint.json.TC