diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index c958aa61f..f570f2947 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# taskana \ No newline at end of file +[![Build Status](https://travis-ci.org/Taskana/hackathon.svg?branch=master)](https://travis-ci.org/Taskana/hackathon) +[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) + +# Hackathon + +## Link to UI +http://taskana-workplace.mybluemix.net diff --git a/admin/.angular-cli.json b/admin/.angular-cli.json new file mode 100644 index 000000000..c5114eea2 --- /dev/null +++ b/admin/.angular-cli.json @@ -0,0 +1,59 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "admin" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "../node_modules/bootstrap/dist/css/bootstrap.min.css", + "../node_modules/bootstrap-vertical-tabs/bootstrap.vertical-tabs.css", + "styles.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/admin/.editorconfig b/admin/.editorconfig new file mode 100644 index 000000000..6e87a003d --- /dev/null +++ b/admin/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 000000000..54bfd2001 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/admin/.vscode/launch.json b/admin/.vscode/launch.json new file mode 100644 index 000000000..02e2d3199 --- /dev/null +++ b/admin/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Node.js-Debugattribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Programm starten", + "program": "${file}", + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ] + } + ] +} \ No newline at end of file diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 000000000..9637185d5 --- /dev/null +++ b/admin/README.md @@ -0,0 +1,28 @@ +# Admin + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.3. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Before running the tests make sure you are serving the app via `ng serve`. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/admin/e2e/app.e2e-spec.ts b/admin/e2e/app.e2e-spec.ts new file mode 100644 index 000000000..fe2cf3ad0 --- /dev/null +++ b/admin/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { AdminPage } from './app.po'; + +describe('admin App', () => { + let page: AdminPage; + + beforeEach(() => { + page = new AdminPage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/admin/e2e/app.po.ts b/admin/e2e/app.po.ts new file mode 100644 index 000000000..3de9d5cf7 --- /dev/null +++ b/admin/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AdminPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/admin/e2e/tsconfig.e2e.json b/admin/e2e/tsconfig.e2e.json new file mode 100644 index 000000000..e2a9a2fc7 --- /dev/null +++ b/admin/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/admin/karma.conf.js b/admin/karma.conf.js new file mode 100644 index 000000000..84b4cd5ac --- /dev/null +++ b/admin/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/admin/package-lock.json b/admin/package-lock.json new file mode 100644 index 000000000..540598e67 --- /dev/null +++ b/admin/package-lock.json @@ -0,0 +1,5754 @@ +{ + "name": "admin", + "version": "0.0.0", + "lockfileVersion": 1, + "dependencies": { + "@angular/cli": { + "version": "https://registry.npmjs.org/@angular/cli/-/cli-1.0.3.tgz", + "integrity": "sha1-c6S0Py6o5yD1LxBB5egzyuH7KR8=", + "dev": true + }, + "@angular/common": { + "version": "https://registry.npmjs.org/@angular/common/-/common-4.2.4.tgz", + "integrity": "sha1-m37g5pKrAxkafNmvMx0PSS087+s=" + }, + "@angular/compiler": { + "version": "https://registry.npmjs.org/@angular/compiler/-/compiler-4.2.4.tgz", + "integrity": "sha1-eTaoey1IOrzvhAuGjB/PZyIcSRQ=" + }, + "@angular/compiler-cli": { + "version": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-4.2.4.tgz", + "integrity": "sha1-zOlBooNi/BwEKrhYkPyqseIz3Vc=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "@angular/core": { + "version": "https://registry.npmjs.org/@angular/core/-/core-4.2.4.tgz", + "integrity": "sha1-6N96jdXeWjJ3hN/DW12mNEKBzxs=" + }, + "@angular/forms": { + "version": "https://registry.npmjs.org/@angular/forms/-/forms-4.2.4.tgz", + "integrity": "sha1-rgl1UzLqRj9WHfAarCvTaeXTXck=" + }, + "@angular/http": { + "version": "https://registry.npmjs.org/@angular/http/-/http-4.2.4.tgz", + "integrity": "sha1-7QOcDVOlh9JFdfTKv9Hbjn8eP4s=" + }, + "@angular/platform-browser": { + "version": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.2.4.tgz", + "integrity": "sha1-AKRNyy/Xd7VLwXwc5zSqGP6f0Dk=" + }, + "@angular/platform-browser-dynamic": { + "version": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.2.4.tgz", + "integrity": "sha1-UZD9p5QlrkmkkiSYVhTJeAo3B2E=" + }, + "@angular/router": { + "version": "https://registry.npmjs.org/@angular/router/-/router-4.2.4.tgz", + "integrity": "sha1-Lxk/OjbaGbEaCMm0Djkb2P3lnFY=" + }, + "@angular/tsc-wrapped": { + "version": "https://registry.npmjs.org/@angular/tsc-wrapped/-/tsc-wrapped-4.2.4.tgz", + "integrity": "sha1-lW/xTM8gQyQ7DMgjNrIh2+YxWu8=", + "dev": true + }, + "@ngtools/json-schema": { + "version": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.0.9.tgz", + "integrity": "sha1-GeRttAnGa0xDhB6rUU/5ZAhxr/w=", + "dev": true + }, + "@ngtools/webpack": { + "version": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-1.3.1.tgz", + "integrity": "sha1-EhSba5yzv4WPSyrpZQRW4XZ00us=", + "dev": true + }, + "@types/jasmine": { + "version": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.38.tgz", + "integrity": "sha1-pDeRJMSSHU4h3lTsdGacnps1Zxc=", + "dev": true + }, + "@types/node": { + "version": "https://registry.npmjs.org/@types/node/-/node-6.0.78.tgz", + "integrity": "sha1-XUo/V5wVJOAe4hv0dOb7oJGY9HA=", + "dev": true + }, + "@types/q": { + "version": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.42.tgz", + "integrity": "sha1-dMt3+2BS7a/yqJhN2v2I1BnyXKw=", + "dev": true + }, + "abbrev": { + "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "accepts": { + "version": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "dev": true + }, + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + }, + "acorn-dynamic-import": { + "version": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true + }, + "adm-zip": { + "version": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", + "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", + "dev": true + }, + "after": { + "version": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-5.2.0.tgz", + "integrity": "sha1-wXNQJMXaLvdcwZBxMHPUTwmL9IY=", + "dev": true + }, + "ajv-keywords": { + "version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true + }, + "alphanum-sort": { + "version": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "amdefine": { + "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "angular-tree-component": { + "version": "https://registry.npmjs.org/angular-tree-component/-/angular-tree-component-3.7.3.tgz", + "integrity": "sha1-Nl+6rmMS1C1WsgAmAo55VDL0sPQ=" + }, + "ansi-align": { + "version": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true + }, + "ansi-escapes": { + "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", + "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", + "dev": true + }, + "ansi-html": { + "version": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "any-promise": { + "version": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "anymatch": { + "version": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", + "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", + "dev": true + }, + "app-root-path": { + "version": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", + "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", + "dev": true + }, + "append-transform": { + "version": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "dev": true + }, + "aproba": { + "version": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz", + "integrity": "sha1-RcZikJTeTpb2k+9+q3SuB5wkD8E=", + "dev": true + }, + "are-we-there-yet": { + "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true + }, + "argparse": { + "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true + }, + "arr-diff": { + "version": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true + }, + "arr-flatten": { + "version": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", + "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", + "dev": true + }, + "array-find-index": { + "version": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true, + "optional": true + }, + "array-flatten": { + "version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "array-slice": { + "version": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-union": { + "version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true + }, + "array-uniq": { + "version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arraybuffer.slice": { + "version": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "dev": true + }, + "arrify": { + "version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz", + "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "asn1.js": { + "version": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "dev": true + }, + "assert": { + "version": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.4.1.tgz", + "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c=", + "dev": true + }, + "async-each": { + "version": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-foreach": { + "version": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "autoprefixer": { + "version": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "dev": true + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "dev": true + }, + "babel-messages": { + "version": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true + }, + "babel-runtime": { + "version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", + "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", + "dev": true + }, + "babel-template": { + "version": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", + "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", + "dev": true + }, + "babel-traverse": { + "version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", + "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", + "dev": true + }, + "babel-types": { + "version": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", + "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", + "dev": true + }, + "babylon": { + "version": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", + "integrity": "sha1-Pot0AriNIsNCPhN6FXeIOxX/hpo=", + "dev": true + }, + "backo2": { + "version": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "base64-arraybuffer": { + "version": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY=", + "dev": true + }, + "base64id": { + "version": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true + }, + "better-assert": { + "version": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true + }, + "big.js": { + "version": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", + "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=", + "dev": true + }, + "binary-extensions": { + "version": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", + "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", + "dev": true + }, + "blob": { + "version": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "block-stream": { + "version": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true + }, + "blocking-proxy": { + "version": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-0.0.5.tgz", + "integrity": "sha1-RikF4Nz76pcPQao3Ij3anAexkSs=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "bluebird": { + "version": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "dev": true + }, + "bn.js": { + "version": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.7.tgz", + "integrity": "sha1-3bBI5Q2UgnkAlME+s/z8gzznq0Y=", + "dev": true + }, + "body-parser": { + "version": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", + "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", + "dev": true, + "dependencies": { + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", + "dev": true + } + } + }, + "boolbase": { + "version": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true + }, + "bootstrap": { + "version": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", + "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" + }, + "bootstrap-vertical-tabs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bootstrap-vertical-tabs/-/bootstrap-vertical-tabs-1.2.2.tgz", + "integrity": "sha1-+Rdvbzc3GS+xVy8YlfQmQ3O9ACM=" + }, + "boxen": { + "version": "https://registry.npmjs.org/boxen/-/boxen-1.1.0.tgz", + "integrity": "sha1-sbad1SIwXoB6md7ud329blFnsQI=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "dependencies": { + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + } + } + }, + "braces": { + "version": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true + }, + "brorand": { + "version": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", + "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=", + "dev": true + }, + "browserify-cipher": { + "version": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dev": true + }, + "browserify-des": { + "version": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dev": true + }, + "browserify-rsa": { + "version": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true + }, + "browserify-sign": { + "version": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true + }, + "browserify-zlib": { + "version": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true + }, + "browserslist": { + "version": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true + }, + "buffer": { + "version": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true + }, + "buffer-xor": { + "version": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz", + "integrity": "sha1-1baAoWW2IBc5rLYRVCqrwtjOsHA=", + "dev": true + }, + "callsite": { + "version": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camel-case": { + "version": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true + }, + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "optional": true + }, + "camelcase-keys": { + "version": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "optional": true + }, + "caniuse-api": { + "version": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", + "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "dev": true + }, + "caniuse-db": { + "version": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000693.tgz", + "integrity": "sha1-hRDnqasErcyiOl3O+jTfnSjBziA=", + "dev": true + }, + "capture-stack-trace": { + "version": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "dev": true + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "dependencies": { + "lazy-cache": { + "version": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + } + } + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "chokidar": { + "version": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true + }, + "cipher-base": { + "version": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.3.tgz", + "integrity": "sha1-7qvxlEGc6QDaMBjCB9IS8qbfCgc=", + "dev": true + }, + "clap": { + "version": "https://registry.npmjs.org/clap/-/clap-1.2.0.tgz", + "integrity": "sha1-WckP4+E3EEdG/xlGmiemNP9oyFc=", + "dev": true + }, + "clean-css": { + "version": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.4.tgz", + "integrity": "sha1-7siBHbJ0V+AHjYypIfqBty+oK/Q=", + "dev": true + }, + "cli-boxes": { + "version": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cli-cursor": { + "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true + }, + "cli-width": { + "version": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", + "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", + "dev": true + }, + "cliui": { + "version": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + } + } + }, + "clone": { + "version": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", + "dev": true + }, + "clone-deep": { + "version": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz", + "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=", + "dev": true + }, + "co": { + "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "coa": { + "version": "https://registry.npmjs.org/coa/-/coa-1.0.3.tgz", + "integrity": "sha1-G1Sl4dz3fJkEVdTe6pjFZEFtyJM=", + "dev": true + }, + "code-point-at": { + "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codelyzer": { + "version": "https://registry.npmjs.org/codelyzer/-/codelyzer-2.0.1.tgz", + "integrity": "sha1-0PcSH2eoQkyS0h07MfNkC4Pe+e0=", + "dev": true + }, + "color": { + "version": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "dev": true + }, + "color-convert": { + "version": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true + }, + "color-name": { + "version": "https://registry.npmjs.org/color-name/-/color-name-1.1.2.tgz", + "integrity": "sha1-XIq3K2S9IhXWF66VWeuxSEdc+Y0=", + "dev": true + }, + "color-string": { + "version": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true + }, + "colormin": { + "version": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", + "dev": true + }, + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combine-lists": { + "version": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true + }, + "combined-stream": { + "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true + }, + "commander": { + "version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true + }, + "common-tags": { + "version": "https://registry.npmjs.org/common-tags/-/common-tags-1.4.0.tgz", + "integrity": "sha1-EYe+Tz1M8MBCfUP3Tu8fc1AWFMA=", + "dev": true + }, + "component-bind": { + "version": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", + "dev": true + }, + "component-inherit": { + "version": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compressible": { + "version": "https://registry.npmjs.org/compressible/-/compressible-2.0.10.tgz", + "integrity": "sha1-/tocf3YXkScyspv4zyYlKiC57s0=", + "dev": true + }, + "compression": { + "version": "https://registry.npmjs.org/compression/-/compression-1.6.2.tgz", + "integrity": "sha1-zOsSHsydCcUtetDDNQ6pPd1AK8M=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "configstore": { + "version": "https://registry.npmjs.org/configstore/-/configstore-3.1.0.tgz", + "integrity": "sha1-Rd+QcHPibfoc9LLVL1tgVF6qEdE=", + "dev": true + }, + "connect": { + "version": "https://registry.npmjs.org/connect/-/connect-3.6.2.tgz", + "integrity": "sha1-aU6NIGgb/kkCgsiriGvpjwn0L+c=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz", + "integrity": "sha1-5R0X+PDvDbkKZP20feMFFVbp8Wk=", + "dev": true + }, + "console-browserify": { + "version": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true + }, + "console-control-strings": { + "version": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "constants-browserify": { + "version": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=", + "dev": true + }, + "convert-source-map": { + "version": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", + "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", + "dev": true + }, + "cookie": { + "version": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "core-js": { + "version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.1.3.tgz", + "integrity": "sha1-lSdx6w3dwcs/ovb75RpSLpOz7go=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "create-ecdh": { + "version": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dev": true + }, + "create-error-class": { + "version": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true + }, + "create-hash": { + "version": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "dev": true + }, + "create-hmac": { + "version": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "dev": true + }, + "cross-spawn": { + "version": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "optional": true + }, + "cross-spawn-async": { + "version": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz", + "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=", + "dev": true + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true + }, + "crypto-browserify": { + "version": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.0.tgz", + "integrity": "sha1-NlKgkGq5sqfgw85mpAjpV6JIVSI=", + "dev": true + }, + "crypto-random-string": { + "version": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "css-color-names": { + "version": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-loader": { + "version": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.4.tgz", + "integrity": "sha1-bPNXkZLONV6LONX0Ldeh8uyJjQ8=", + "dev": true + }, + "css-parse": { + "version": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-select": { + "version": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true + }, + "css-selector-tokenizer": { + "version": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true + }, + "css-what": { + "version": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "cssauron": { + "version": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true + }, + "cssesc": { + "version": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cssnano": { + "version": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "dev": true + }, + "csso": { + "version": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "dev": true + }, + "currently-unhandled": { + "version": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "optional": true + }, + "custom-event": { + "version": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "dashdash": { + "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "date-now": { + "version": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true + }, + "decamelize": { + "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-extend": { + "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "default-require-extensions": { + "version": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "dev": true + }, + "defined": { + "version": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true + }, + "delayed-stream": { + "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "denodeify": { + "version": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", + "dev": true + }, + "depd": { + "version": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", + "dev": true + }, + "des.js": { + "version": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true + }, + "destroy": { + "version": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true + }, + "detect-node": { + "version": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", + "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", + "dev": true + }, + "di": { + "version": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "diffie-hellman": { + "version": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dev": true + }, + "directory-encoder": { + "version": "https://registry.npmjs.org/directory-encoder/-/directory-encoder-0.7.2.tgz", + "integrity": "sha1-WbTiqk8lQi9sY7UntGL14tDdLFg=", + "dev": true, + "dependencies": { + "fs-extra": { + "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.23.1.tgz", + "integrity": "sha1-ZhHbpq3yq43Jxp+rN83fiBgVfj0=", + "dev": true + } + } + }, + "dom-converter": { + "version": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", + "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", + "dev": true, + "dependencies": { + "utila": { + "version": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", + "dev": true + } + } + }, + "dom-serialize": { + "version": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true + }, + "dom-serializer": { + "version": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "dependencies": { + "domelementtype": { + "version": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domain-browser": { + "version": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "domelementtype": { + "version": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", + "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", + "dev": true + }, + "domutils": { + "version": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true + }, + "dot-prop": { + "version": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.1.1.tgz", + "integrity": "sha1-qEk/C3te7sglJbXHWH+n3nyoWcE=", + "dev": true + }, + "duplexer3": { + "version": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ecc-jsbn": { + "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true + }, + "ee-first": { + "version": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.14.tgz", + "integrity": "sha1-ZK8Pnv08PGrNV9cfg7Scp+6cS0M=", + "dev": true + }, + "elliptic": { + "version": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true + }, + "ember-cli-normalize-entity-name": { + "version": "https://registry.npmjs.org/ember-cli-normalize-entity-name/-/ember-cli-normalize-entity-name-1.0.0.tgz", + "integrity": "sha1-CxT3vLxZmqEXtf3cgeT9A8S61bc=", + "dev": true + }, + "ember-cli-string-utils": { + "version": "https://registry.npmjs.org/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz", + "integrity": "sha1-ObZ3/CgF9VFzc1N2/O8njqpEUqE=", + "dev": true + }, + "emojis-list": { + "version": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", + "dev": true + }, + "engine.io": { + "version": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.2.tgz", + "integrity": "sha1-a1m+cws0jAElsKRYneHDVavPen4=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.2.tgz", + "integrity": "sha1-w4dnVH8qfRhPV1L28K1QEAZwN2Y=", + "dev": true, + "dependencies": { + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", + "dev": true + }, + "enhanced-resolve": { + "version": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz", + "integrity": "sha1-n0tib1dyRe3PSyrYPYbhf09CHew=", + "dev": true + }, + "ensure-posix-path": { + "version": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.0.2.tgz", + "integrity": "sha1-pls+QtC3HPxYXrd0+ZQ8jZuRsMI=", + "dev": true + }, + "ent": { + "version": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "errno": { + "version": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dev": true + }, + "error-ex": { + "version": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true + }, + "escape-html": { + "version": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esutils": { + "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", + "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=", + "dev": true + }, + "eventemitter3": { + "version": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true + }, + "events": { + "version": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "eventsource": { + "version": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "dev": true + }, + "evp_bytestokey": { + "version": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz", + "integrity": "sha1-SXtmrZ/vZc18CKYYCCS6FHa2blM=", + "dev": true + }, + "execa": { + "version": "https://registry.npmjs.org/execa/-/execa-0.4.0.tgz", + "integrity": "sha1-TrZGejaglfq7KXD/nV4/t7zm68M=", + "dev": true + }, + "exit": { + "version": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-braces": { + "version": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "dependencies": { + "braces": { + "version": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true + }, + "expand-range": { + "version": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true + }, + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + }, + "repeat-string": { + "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true + }, + "expand-range": { + "version": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true + }, + "exports-loader": { + "version": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.6.4.tgz", + "integrity": "sha1-1w/GEhl1s1/BKDDPUnVL4nQPyIY=", + "dev": true + }, + "express": { + "version": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", + "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "dev": true + } + } + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "external-editor": { + "version": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", + "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", + "dev": true + }, + "extglob": { + "version": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true + }, + "extract-text-webpack-plugin": { + "version": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.2.tgz", + "integrity": "sha1-dW7076gVXDaBgz+8NNpTuUF0bWw=", + "dev": true + }, + "extsprintf": { + "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "fast-deep-equal": { + "version": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-0.1.0.tgz", + "integrity": "sha1-XG9FmaumszPuM0Li7ZeGcvEAH40=", + "dev": true + }, + "fastparse": { + "version": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "faye-websocket": { + "version": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true + }, + "figures": { + "version": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true + }, + "file-loader": { + "version": "https://registry.npmjs.org/file-loader/-/file-loader-0.10.1.tgz", + "integrity": "sha1-gVA0EZiR/GRB+1pkwRvJPCLd2EI=", + "dev": true + }, + "filename-regex": { + "version": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true + }, + "fill-range": { + "version": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "dependencies": { + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true + } + } + }, + "finalhandler": { + "version": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", + "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "dev": true + } + } + }, + "find-up": { + "version": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true + }, + "findup-sync": { + "version": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true + } + } + }, + "flatten": { + "version": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", + "dev": true + }, + "for-in": { + "version": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true + }, + "forever-agent": { + "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true + }, + "forwarded": { + "version": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", + "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=", + "dev": true + }, + "fresh": { + "version": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=", + "dev": true + }, + "fs-access": { + "version": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true + }, + "fs-extra": { + "version": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", + "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", + "dev": true + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", + "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "aproba": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "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=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "optional": true + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "optional": true + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "dev": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "optional": 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=", + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.36", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", + "dev": true, + "optional": true + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "optional": true + }, + "npmlog": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", + "dev": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true, + "optional": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dev": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "dev": true + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true + }, + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "optional": true + }, + "sshpk": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true + }, + "tar-pack": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dev": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } + }, + "fstream": { + "version": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true + }, + "function-bind": { + "version": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", + "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", + "dev": true + }, + "gauge": { + "version": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + } + } + }, + "gaze": { + "version": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", + "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-stdin": { + "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "getpass": { + "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true + }, + "glob-base": { + "version": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true + }, + "glob-parent": { + "version": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true + }, + "globals": { + "version": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "dev": true + }, + "globby": { + "version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true + }, + "globule": { + "version": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "dev": true, + "optional": true + }, + "got": { + "version": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true + }, + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "handle-thing": { + "version": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", + "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", + "dev": true + }, + "handlebars": { + "version": "https://registry.npmjs.org/handlebars/-/handlebars-1.3.0.tgz", + "integrity": "sha1-npsTCpPjiUkTItl1zz7BgYw3zjQ=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true, + "optional": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz", + "integrity": "sha1-+gmEdwtCi3qbKoBY9GNV0U/vIRo=", + "dev": true, + "optional": true + } + } + }, + "har-schema": { + "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "dependencies": { + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true + } + } + }, + "has": { + "version": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true + }, + "has-binary": { + "version": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "has-cors": { + "version": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-unicode": { + "version": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "hash-base": { + "version": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "dev": true + }, + "hash.js": { + "version": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.1.tgz", + "integrity": "sha1-XLLnlkmSJOaf0LAO0B0tShbnoyM=", + "dev": true + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true + }, + "he": { + "version": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "hosted-git-info": { + "version": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", + "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=", + "dev": true + }, + "hpack.js": { + "version": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true + }, + "html-comment-regex": { + "version": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", + "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", + "dev": true + }, + "html-entities": { + "version": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-minifier": { + "version": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.2.tgz", + "integrity": "sha1-1zvD/0SJQkCIGM5gm/P7DqfvTrc=", + "dev": true + }, + "html-webpack-plugin": { + "version": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.28.0.tgz", + "integrity": "sha1-LnhjtX5f1I/iYzA+L/yTTDBk0Ak=", + "dev": true, + "dependencies": { + "loader-utils": { + "version": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true + } + } + }, + "htmlparser2": { + "version": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", + "dev": true, + "dependencies": { + "domutils": { + "version": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", + "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "http-deceiver": { + "version": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", + "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", + "dev": true + }, + "http-proxy": { + "version": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", + "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "dev": true + }, + "http-proxy-middleware": { + "version": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", + "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", + "dev": true, + "dependencies": { + "is-extglob": { + "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true + } + } + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true + }, + "https-browserify": { + "version": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=", + "dev": true + }, + "https-proxy-agent": { + "version": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", + "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", + "dev": true, + "dependencies": { + "agent-base": { + "version": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", + "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", + "dev": true + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", + "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", + "dev": true + } + } + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=", + "dev": true + }, + "icss-replace-symbols": { + "version": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "dev": true, + "dependencies": { + "postcss": { + "version": "https://registry.npmjs.org/postcss/-/postcss-6.0.2.tgz", + "integrity": "sha1-XE/qWJ8Kw7AMqnWxy8OihBlbfl0=", + "dev": true + } + } + }, + "ieee754": { + "version": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "image-size": { + "version": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "img-stats": { + "version": "https://registry.npmjs.org/img-stats/-/img-stats-0.5.2.tgz", + "integrity": "sha1-wgNJbELy2esuWrgjL6dWurMsnis=", + "dev": true + }, + "import-lazy": { + "version": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true, + "optional": true + }, + "indent-string": { + "version": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "optional": true + }, + "indexes-of": { + "version": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflection": { + "version": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", + "dev": true + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "inquirer": { + "version": "https://registry.npmjs.org/inquirer/-/inquirer-3.1.1.tgz", + "integrity": "sha1-h2IcT7pAcvSKjdccn5328QCy1TQ=", + "dev": true + }, + "interpret": { + "version": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", + "dev": true + }, + "invariant": { + "version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "dev": true + }, + "invert-kv": { + "version": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ipaddr.js": { + "version": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", + "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=", + "dev": true + }, + "is-absolute-url": { + "version": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-arrayish": { + "version": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true + }, + "is-buffer": { + "version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-builtin-module": { + "version": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true + }, + "is-directory": { + "version": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-dotfile": { + "version": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true + }, + "is-extendable": { + "version": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true + }, + "is-npm": { + "version": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true + }, + "is-obj": { + "version": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-cwd": { + "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true + }, + "is-path-inside": { + "version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true + }, + "is-plain-obj": { + "version": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.3.tgz", + "integrity": "sha1-wVvz5LZrYtcu+vKSWEhmPsvGGbY=", + "dev": true + }, + "is-posix-bracket": { + "version": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-redirect": { + "version": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-retry-allowed": { + "version": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, + "is-stream": { + "version": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-svg": { + "version": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "dev": true + }, + "is-typedarray": { + "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true + }, + "isexe": { + "version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "https://registry.npmjs.org/isobject/-/isobject-3.0.0.tgz", + "integrity": "sha1-OVZSF/NmF4nooKDAgNX35rxG4aA=", + "dev": true + }, + "isstream": { + "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.1.9.tgz", + "integrity": "sha1-KCeSDTgNQobYV9V6KWioQduKfsg=", + "dev": true + }, + "istanbul-instrumenter-loader": { + "version": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-2.0.0.tgz", + "integrity": "sha1-5UkpAKsLuoNe+oAkywC+mz7qJwA=", + "dev": true, + "dependencies": { + "loader-utils": { + "version": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true + } + } + }, + "istanbul-lib-coverage": { + "version": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz", + "integrity": "sha1-c7+5mIhSmUFck9OKPprfeEp3qdo=", + "dev": true + }, + "istanbul-lib-hook": { + "version": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz", + "integrity": "sha1-3WYH8DB2V4/n1vKmMM8UO0m6zdw=", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz", + "integrity": "sha1-YBSwPTRw+3djjVgCUIwlXAYxLlY=", + "dev": true, + "dependencies": { + "babel-generator": { + "version": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", + "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=", + "dev": true + }, + "jsesc": { + "version": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", + "integrity": "sha1-8OVfVmVf+jQiIIC3oM1HYOFAX8k=", + "dev": true + }, + "istanbul-lib-source-maps": { + "version": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz", + "integrity": "sha1-pv4ay6jOCO68Y45XLilNJnAIqgw=", + "dev": true + }, + "istanbul-reports": { + "version": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha1-BCvlyJ4XW8P4ZSPKqynAFOd/7k4=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "cliui": { + "version": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "dependencies": { + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + } + } + }, + "handlebars": { + "version": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", + "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "dev": true + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "dependencies": { + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true, + "optional": true + } + } + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true + } + } + }, + "jasmine": { + "version": "https://registry.npmjs.org/jasmine/-/jasmine-2.6.0.tgz", + "integrity": "sha1-ayLnCIPo5YnUVjRhU7TSBt2+IX8=", + "dev": true, + "dependencies": { + "jasmine-core": { + "version": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.6.4.tgz", + "integrity": "sha1-3skmzQqfoof7bbXHVfpIfnTOysU=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.5.2.tgz", + "integrity": "sha1-b2G9eQYeJ/Q+b5NV5Es8bKtv8pc=", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-3.2.0.tgz", + "integrity": "sha1-/b6FqAzN07J2dGvHf96Dwc53Pv8=", + "dev": true + }, + "jasminewd2": { + "version": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.1.0.tgz", + "integrity": "sha1-2llSddGuYx3nNqwKfH2Fyfc+9lI=", + "dev": true + }, + "js-base64": { + "version": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", + "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=", + "dev": true + }, + "js-tokens": { + "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", + "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=", + "dev": true + }, + "js-yaml": { + "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true + }, + "jsbn": { + "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jschardet": { + "version": "https://registry.npmjs.org/jschardet/-/jschardet-1.4.2.tgz", + "integrity": "sha1-KqEH8UKvQSHRRWWdRPUIMJYeaZo=", + "dev": true + }, + "jsesc": { + "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "json-loader": { + "version": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.4.tgz", + "integrity": "sha1-i6oTZaYy9Yo8RtIBdfxgAsluN94=", + "dev": true + }, + "json-schema": { + "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.0.tgz", + "integrity": "sha1-ABbAscoe/kbUTTdUG838Gdz64Ns=", + "dev": true + }, + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true + }, + "json-stringify-safe": { + "version": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true + }, + "jsonify": { + "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsprim": { + "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dev": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "karma": { + "version": "https://registry.npmjs.org/karma/-/karma-1.4.1.tgz", + "integrity": "sha1-QZgacdVCN2BrCj6oxYyQdz9BZQ4=", + "dev": true, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true + }, + "tmp": { + "version": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", + "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", + "dev": true + } + } + }, + "karma-chrome-launcher": { + "version": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz", + "integrity": "sha1-IWh5xorATY1RQOmWGboEtZr9Rs8=", + "dev": true + }, + "karma-cli": { + "version": "https://registry.npmjs.org/karma-cli/-/karma-cli-1.0.1.tgz", + "integrity": "sha1-rmw8WKMTodALRRZMRVubhs4X+WA=", + "dev": true + }, + "karma-coverage-istanbul-reporter": { + "version": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-0.2.3.tgz", + "integrity": "sha1-EfG+nPqTdVp3usOasW4xWnEAtcU=", + "dev": true + }, + "karma-jasmine": { + "version": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.0.tgz", + "integrity": "sha1-IuTAa/mhguUpTR9wXjczgRuBCs8=", + "dev": true + }, + "karma-jasmine-html-reporter": { + "version": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", + "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", + "dev": true + }, + "karma-sourcemap-loader": { + "version": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz", + "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", + "dev": true + }, + "karma-webpack": { + "version": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.3.tgz", + "integrity": "sha1-Oc6/XKJYATmyf5rmm3iBa5yC+uY=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "loader-utils": { + "version": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true + } + } + }, + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true + }, + "latest-version": { + "version": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true + }, + "lazy-cache": { + "version": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", + "dev": true + }, + "lcid": { + "version": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true + }, + "less": { + "version": "https://registry.npmjs.org/less/-/less-2.7.2.tgz", + "integrity": "sha1-No1sxz4fsDmBGDKAkYdDxdz5s98=", + "dev": true + }, + "less-loader": { + "version": "https://registry.npmjs.org/less-loader/-/less-loader-4.0.4.tgz", + "integrity": "sha1-tKjEOEPmXGfS6i6xRltcQjPVAGo=", + "dev": true, + "dependencies": { + "clone": { + "version": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + } + } + }, + "load-json-file": { + "version": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true + }, + "loader-runner": { + "version": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "dev": true + }, + "loader-utils": { + "version": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash-es": { + "version": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", + "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" + }, + "lodash.assign": { + "version": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true, + "optional": true + }, + "lodash.camelcase": { + "version": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.clonedeep": { + "version": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.memoize": { + "version": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.mergewith": { + "version": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", + "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=", + "dev": true, + "optional": true + }, + "lodash.tail": { + "version": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "lodash.uniq": { + "version": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log4js": { + "version": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", + "dev": true, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "longest": { + "version": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true + }, + "loud-rejection": { + "version": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "optional": true + }, + "lower-case": { + "version": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lowercase-keys": { + "version": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + }, + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=", + "dev": true + }, + "macaddress": { + "version": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", + "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", + "dev": true + }, + "magic-string": { + "version": "https://registry.npmjs.org/magic-string/-/magic-string-0.19.1.tgz", + "integrity": "sha1-FNdoATyvLsj96hakmvgvw3fnUgE=", + "dev": true + }, + "make-dir": { + "version": "https://registry.npmjs.org/make-dir/-/make-dir-1.0.0.tgz", + "integrity": "sha1-l6ARdR6R3YfPre9Ygy67BJNt6Xg=", + "dev": true + }, + "make-error": { + "version": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz", + "integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=", + "dev": true + }, + "map-obj": { + "version": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "matcher-collection": { + "version": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-1.0.4.tgz", + "integrity": "sha1-L2auCGmZbynkPQtiyD3R1D5YF1U=", + "dev": true + }, + "math-expression-evaluator": { + "version": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", + "dev": true + }, + "media-typer": { + "version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memory-fs": { + "version": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true + }, + "meow": { + "version": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "optional": true + } + } + }, + "merge-descriptors": { + "version": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true + }, + "miller-rabin": { + "version": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", + "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", + "dev": true + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", + "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=", + "dev": true + }, + "mime-db": { + "version": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "dev": true + }, + "mime-types": { + "version": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dev": true + }, + "mimic-fn": { + "version": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimalistic-assert": { + "version": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mixin-object": { + "version": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "dependencies": { + "for-in": { + "version": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true + }, + "mobx": { + "version": "https://registry.npmjs.org/mobx/-/mobx-3.1.11.tgz", + "integrity": "sha1-QAsePACkJJ3+Num8oo7zbwV9Sig=" + }, + "mobx-angular": { + "version": "https://registry.npmjs.org/mobx-angular/-/mobx-angular-1.5.0.tgz", + "integrity": "sha1-bIBkg+v30WHltfUEjtwwD4UhyAw=" + }, + "moment": { + "version": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", + "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", + "dev": true, + "optional": true + }, + "ncname": { + "version": "https://registry.npmjs.org/ncname/-/ncname-1.0.0.tgz", + "integrity": "sha1-W1etGLHKCShk72Kwse2BlPODtxw=", + "dev": true + }, + "negotiator": { + "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "ngx-bootstrap": { + "version": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-1.7.1.tgz", + "integrity": "sha1-9I5dgcQOjh8lq9Pns4gI7p7I6QM=" + }, + "no-case": { + "version": "https://registry.npmjs.org/no-case/-/no-case-2.3.1.tgz", + "integrity": "sha1-euuhxzpSGEJlVUt9wDuvcg34AIE=", + "dev": true + }, + "node-gyp": { + "version": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", + "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", + "dev": true, + "optional": true, + "dependencies": { + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "optional": true + } + } + }, + "node-libs-browser": { + "version": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", + "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", + "dev": true, + "dependencies": { + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "node-modules-path": { + "version": "https://registry.npmjs.org/node-modules-path/-/node-modules-path-1.0.1.tgz", + "integrity": "sha1-QAlrCM560OoUaAhjr0ScfHWl0cg=", + "dev": true + }, + "node-sass": { + "version": "https://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz", + "integrity": "sha1-0JydEXlkEjnRuX/8YjH9zsU+FWg=", + "dev": true, + "optional": true + }, + "nopt": { + "version": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true + }, + "normalize-package-data": { + "version": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", + "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", + "dev": true + }, + "normalize-path": { + "version": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true + }, + "normalize-range": { + "version": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true + }, + "npm-run-path": { + "version": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", + "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", + "dev": true + }, + "npmlog": { + "version": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha1-3Fm+6F9k8A7UJO+yrweD3yXRwLU=", + "dev": true + }, + "nth-check": { + "version": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true + }, + "null-check": { + "version": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, + "num2fraction": { + "version": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object.omit": { + "version": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "dependencies": { + "for-own": { + "version": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true + } + } + }, + "obuf": { + "version": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", + "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=", + "dev": true + }, + "on-finished": { + "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true + }, + "on-headers": { + "version": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true + }, + "onetime": { + "version": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true + }, + "opn": { + "version": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", + "dev": true + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "dev": true + }, + "options": { + "version": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true + }, + "original": { + "version": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", + "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", + "dev": true, + "dependencies": { + "url-parse": { + "version": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz", + "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", + "dev": true + } + } + }, + "os-browserify": { + "version": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", + "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=", + "dev": true + }, + "os-homedir": { + "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true + }, + "os-tmpdir": { + "version": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true + }, + "package-json": { + "version": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true + }, + "pako": { + "version": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "param-case": { + "version": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true + }, + "parse-asn1": { + "version": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dev": true + }, + "parse-glob": { + "version": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true + }, + "parse-json": { + "version": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true + }, + "parsejson": { + "version": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "dev": true + }, + "parseqs": { + "version": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true + }, + "parseuri": { + "version": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true + }, + "parseurl": { + "version": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=", + "dev": true + }, + "path-browserify": { + "version": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-exists": { + "version": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", + "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=", + "dev": true + }, + "path-parse": { + "version": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-to-regexp": { + "version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true + }, + "pbkdf2": { + "version": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", + "integrity": "sha1-vjZ4XFBn6kjYBv+SMojF91C2uKI=", + "dev": true + }, + "performance-now": { + "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true + }, + "pify": { + "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true + }, + "portfinder": { + "version": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", + "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "postcss": { + "version": "https://registry.npmjs.org/postcss/-/postcss-5.2.17.tgz", + "integrity": "sha1-z09Ze4ZNZcikkrLqvp1wbIecOIs=", + "dev": true + }, + "postcss-calc": { + "version": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "dev": true + }, + "postcss-colormin": { + "version": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", + "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "dev": true + }, + "postcss-convert-values": { + "version": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", + "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "dev": true + }, + "postcss-discard-comments": { + "version": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "dev": true + }, + "postcss-discard-duplicates": { + "version": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", + "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "dev": true + }, + "postcss-discard-empty": { + "version": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "dev": true + }, + "postcss-discard-overridden": { + "version": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "dev": true + }, + "postcss-discard-unused": { + "version": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "dev": true + }, + "postcss-filter-plugins": { + "version": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", + "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", + "dev": true + }, + "postcss-load-config": { + "version": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", + "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", + "dev": true + }, + "postcss-load-options": { + "version": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", + "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", + "dev": true + }, + "postcss-load-plugins": { + "version": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", + "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", + "dev": true + }, + "postcss-loader": { + "version": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-1.3.3.tgz", + "integrity": "sha1-piHqH6KQYqg5cqRvVEhncTAZFus=", + "dev": true + }, + "postcss-merge-idents": { + "version": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "dev": true + }, + "postcss-merge-longhand": { + "version": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", + "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "dev": true + }, + "postcss-merge-rules": { + "version": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", + "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "dev": true + }, + "postcss-message-helpers": { + "version": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "dev": true + }, + "postcss-minify-font-values": { + "version": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "dev": true + }, + "postcss-minify-gradients": { + "version": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "dev": true + }, + "postcss-minify-params": { + "version": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "dev": true + }, + "postcss-minify-selectors": { + "version": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "dev": true + }, + "postcss-modules-extract-imports": { + "version": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", + "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", + "dev": true, + "dependencies": { + "postcss": { + "version": "https://registry.npmjs.org/postcss/-/postcss-6.0.2.tgz", + "integrity": "sha1-XE/qWJ8Kw7AMqnWxy8OihBlbfl0=", + "dev": true + } + } + }, + "postcss-modules-local-by-default": { + "version": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "dependencies": { + "postcss": { + "version": "https://registry.npmjs.org/postcss/-/postcss-6.0.2.tgz", + "integrity": "sha1-XE/qWJ8Kw7AMqnWxy8OihBlbfl0=", + "dev": true + } + } + }, + "postcss-modules-scope": { + "version": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "dependencies": { + "postcss": { + "version": "https://registry.npmjs.org/postcss/-/postcss-6.0.2.tgz", + "integrity": "sha1-XE/qWJ8Kw7AMqnWxy8OihBlbfl0=", + "dev": true + } + } + }, + "postcss-modules-values": { + "version": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "dependencies": { + "postcss": { + "version": "https://registry.npmjs.org/postcss/-/postcss-6.0.2.tgz", + "integrity": "sha1-XE/qWJ8Kw7AMqnWxy8OihBlbfl0=", + "dev": true + } + } + }, + "postcss-normalize-charset": { + "version": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "dev": true + }, + "postcss-normalize-url": { + "version": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "dev": true + }, + "postcss-ordered-values": { + "version": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", + "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "dev": true + }, + "postcss-reduce-idents": { + "version": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", + "dev": true + }, + "postcss-reduce-initial": { + "version": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "dev": true + }, + "postcss-reduce-transforms": { + "version": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "dev": true + }, + "postcss-selector-parser": { + "version": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true + }, + "postcss-svgo": { + "version": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "dev": true + }, + "postcss-unique-selectors": { + "version": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "dev": true + }, + "postcss-url": { + "version": "https://registry.npmjs.org/postcss-url/-/postcss-url-5.1.2.tgz", + "integrity": "sha1-mLMWW+jVkkccsMqt3iwNH4MvEz4=", + "dev": true + }, + "postcss-value-parser": { + "version": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", + "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", + "dev": true + }, + "postcss-zindex": { + "version": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", + "dev": true + }, + "prepend-http": { + "version": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "preserve": { + "version": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-error": { + "version": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true + }, + "process": { + "version": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "promise": { + "version": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "dev": true, + "optional": true + }, + "protractor": { + "version": "https://registry.npmjs.org/protractor/-/protractor-5.1.2.tgz", + "integrity": "sha1-myIXQXCaTGLVzVPGqt1UpxE36V8=", + "dev": true, + "dependencies": { + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true + }, + "q": { + "version": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "webdriver-manager": { + "version": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", + "integrity": "sha1-PfGkgZdwELTL+MnYXHpXeCjA5ws=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + } + } + }, + "proxy-addr": { + "version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", + "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=", + "dev": true + }, + "prr": { + "version": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "dev": true + }, + "pseudomap": { + "version": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dev": true + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "https://registry.npmjs.org/q/-/q-1.5.0.tgz", + "integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=", + "dev": true + }, + "qjobs": { + "version": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz", + "integrity": "sha1-ZZ3p8s+NzCehSBJ28gU3cnI4LnM=", + "dev": true + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true + }, + "query-string": { + "version": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true + }, + "querystring": { + "version": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", + "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", + "dev": true + }, + "randomatic": { + "version": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "dev": true, + "dependencies": { + "is-number": { + "version": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true + } + } + }, + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true + } + } + }, + "randombytes": { + "version": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", + "integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=", + "dev": true + }, + "range-parser": { + "version": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", + "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", + "dev": true, + "dependencies": { + "bytes": { + "version": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + }, + "iconv-lite": { + "version": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", + "dev": true + } + } + }, + "raw-loader": { + "version": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, + "rc": { + "version": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "read-pkg": { + "version": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true + }, + "read-pkg-up": { + "version": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.1.tgz", + "integrity": "sha1-hOJpZbueeFU17SVujTjpLGnwnRA=", + "dev": true + }, + "readdirp": { + "version": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true + }, + "redent": { + "version": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "optional": true + }, + "reduce-css-calc": { + "version": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "dev": true + }, + "reduce-function-call": { + "version": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "dev": true + }, + "reflect-metadata": { + "version": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", + "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=", + "dev": true + }, + "regenerate": { + "version": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", + "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=", + "dev": true + }, + "regenerator-runtime": { + "version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + }, + "regex-cache": { + "version": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", + "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", + "dev": true + }, + "regexpu-core": { + "version": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true + }, + "registry-auth-token": { + "version": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", + "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "dev": true + }, + "registry-url": { + "version": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true + }, + "regjsgen": { + "version": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true + }, + "relateurl": { + "version": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", + "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=", + "dev": true + }, + "renderkid": { + "version": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", + "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", + "dev": true, + "dependencies": { + "utila": { + "version": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", + "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", + "dev": true + } + } + }, + "repeat-element": { + "version": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true + }, + "require-directory": { + "version": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", + "dev": true + }, + "require-main-filename": { + "version": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", + "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", + "dev": true + }, + "restore-cursor": { + "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true + }, + "right-align": { + "version": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true + }, + "ripemd160": { + "version": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "dev": true + }, + "rsvp": { + "version": "https://registry.npmjs.org/rsvp/-/rsvp-3.5.0.tgz", + "integrity": "sha1-pixXOkrk4d/QaX68YkLnnGgeqjQ=", + "dev": true + }, + "run-async": { + "version": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true + }, + "rx-lite": { + "version": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true + }, + "rxjs": { + "version": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.1.tgz", + "integrity": "sha1-ti91fyeURdJloYpY+wpw3JDpFiY=" + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", + "dev": true + }, + "sass-graph": { + "version": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "optional": true + }, + "sass-loader": { + "version": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz", + "integrity": "sha1-6dXmwfFV+qMqSybXqbcQfCJeQPk=", + "dev": true, + "dependencies": { + "pify": { + "version": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "saucelabs": { + "version": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.3.0.tgz", + "integrity": "sha1-0kDoAJ33+ocwbsRXimm6O1xCT+4=", + "dev": true + }, + "sax": { + "version": "https://registry.npmjs.org/sax/-/sax-1.2.2.tgz", + "integrity": "sha1-/YYxojvHgmvvXYcb24c3jJVkeCg=", + "dev": true + }, + "schema-utils": { + "version": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true + }, + "script-loader": { + "version": "https://registry.npmjs.org/script-loader/-/script-loader-0.7.0.tgz", + "integrity": "sha1-aF3H5waeDe56kmdPDrxbD1W6pew=", + "dev": true + }, + "scss-tokenizer": { + "version": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "optional": true, + "dependencies": { + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "optional": true + } + } + }, + "select-hose": { + "version": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz", + "integrity": "sha1-ot6l2kqX9mcuiefKcnbO+jZRR6c=", + "dev": true, + "dependencies": { + "tmp": { + "version": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true + } + } + }, + "semver": { + "version": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "semver-diff": { + "version": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true + }, + "semver-dsl": { + "version": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true + }, + "send": { + "version": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", + "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", + "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", + "dev": true + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", + "dev": true + } + } + }, + "serve-index": { + "version": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz", + "integrity": "sha1-0rKA/FYNYW7oG0i/D6gqvtJIXOc=", + "dev": true + }, + "serve-static": { + "version": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", + "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=", + "dev": true + }, + "set-blocking": { + "version": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setimmediate": { + "version": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "sha.js": { + "version": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", + "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", + "dev": true + }, + "shallow-clone": { + "version": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "dev": true, + "dependencies": { + "kind-of": { + "version": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "dev": true + } + } + }, + "signal-exit": { + "version": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "silent-error": { + "version": "https://registry.npmjs.org/silent-error/-/silent-error-1.1.0.tgz", + "integrity": "sha1-IglwbxyFCp8dENDYQJGLRvJuG8k=", + "dev": true + }, + "slide": { + "version": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true + }, + "socket.io": { + "version": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.2.tgz", + "integrity": "sha1-g7u98ueSY7N4kA2kA+eEPgXcO3E=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-client": { + "version": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.2.tgz", + "integrity": "sha1-Of2ww91FDjIbfkDP2DYS7FM91kQ=", + "dev": true, + "dependencies": { + "component-emitter": { + "version": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", + "dev": true, + "dependencies": { + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "sockjs": { + "version": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz", + "integrity": "sha1-2bKJMWyn33dZXvKZ4HXw+TfrQgc=", + "dev": true, + "dependencies": { + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "dev": true + } + } + }, + "sockjs-client": { + "version": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.2.tgz", + "integrity": "sha1-8CEqhVDkyUaMjM6u79LjSTwDOtU=", + "dev": true, + "dependencies": { + "faye-websocket": { + "version": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true + } + } + }, + "sort-keys": { + "version": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true + }, + "source-list-map": { + "version": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + }, + "source-map-loader": { + "version": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.1.6.tgz", + "integrity": "sha1-wJkD2m1zueU7ftjuUkVZcFHpjpE=", + "dev": true, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "loader-utils": { + "version": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true + } + } + }, + "source-map-support": { + "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", + "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", + "dev": true + }, + "spdx-correct": { + "version": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true + }, + "spdx-expression-parse": { + "version": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "spdy": { + "version": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", + "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", + "dev": true + }, + "spdy-transport": { + "version": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz", + "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=", + "dev": true + }, + "sprintf-js": { + "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "statuses": { + "version": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "stdout-stream": { + "version": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", + "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", + "dev": true, + "optional": true + }, + "stream-browserify": { + "version": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true + }, + "stream-http": { + "version": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=", + "dev": true + }, + "strict-uri-encode": { + "version": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", + "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", + "dev": true, + "dependencies": { + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", + "dev": true + } + } + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", + "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=", + "dev": true + }, + "stringstream": { + "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true + }, + "strip-bom": { + "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true + }, + "strip-eof": { + "version": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "optional": true + }, + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "https://registry.npmjs.org/style-loader/-/style-loader-0.13.2.tgz", + "integrity": "sha1-dFMzhM9pjHEEx5URULSXF63C87s=", + "dev": true + }, + "stylus": { + "version": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true + }, + "sax": { + "version": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true + } + } + }, + "stylus-loader": { + "version": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.1.tgz", + "integrity": "sha1-d/SzT9Aw0lsmF7z1UT21sHMMQIk=", + "dev": true + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true + }, + "svgo": { + "version": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "dev": true + }, + "symbol-observable": { + "version": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + }, + "tapable": { + "version": "https://registry.npmjs.org/tapable/-/tapable-0.2.6.tgz", + "integrity": "sha1-IGvo4YiGC1FEJTdebxrom/sB/Y0=", + "dev": true + }, + "tar": { + "version": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "optional": true + }, + "temp": { + "version": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "dev": true, + "dependencies": { + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "term-size": { + "version": "https://registry.npmjs.org/term-size/-/term-size-0.1.1.tgz", + "integrity": "sha1-hzYLljlsq1dgljcUzaDQy+7K2co=", + "dev": true + }, + "through": { + "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "timed-out": { + "version": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "timers-browserify": { + "version": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", + "integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=", + "dev": true + }, + "tmp": { + "version": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true + }, + "to-array": { + "version": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "toposort": { + "version": "https://registry.npmjs.org/toposort/-/toposort-1.0.3.tgz", + "integrity": "sha1-8CzYp0vYvi/A6YYRw7rLlaFxhpw=", + "dev": true + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dev": true + }, + "trim-newlines": { + "version": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "optional": true + }, + "trim-right": { + "version": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "ts-node": { + "version": "https://registry.npmjs.org/ts-node/-/ts-node-2.0.0.tgz", + "integrity": "sha1-FuT+zJSQiCOLTL8cOclYJSa2b3Q=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "tsconfig": { + "version": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz", + "integrity": "sha1-X0J45wGACWeo/Dg/0ZZIh48qbjo=", + "dev": true + }, + "tsickle": { + "version": "https://registry.npmjs.org/tsickle/-/tsickle-0.21.6.tgz", + "integrity": "sha1-U7Abl5xcE/2xOvs/uVgXflmRWI0=", + "dev": true, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "tslib": { + "version": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", + "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=" + }, + "tslint": { + "version": "https://registry.npmjs.org/tslint/-/tslint-4.5.1.tgz", + "integrity": "sha1-BTVocb7yOkNJBnNABvwYgza6gks=", + "dev": true, + "dependencies": { + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true + } + } + }, + "tsutils": { + "version": "https://registry.npmjs.org/tsutils/-/tsutils-1.9.1.tgz", + "integrity": "sha1-ufmrROVa+WgYMdXyjQrur1x1DLA=", + "dev": true + }, + "tty-browserify": { + "version": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true + }, + "tweetnacl": { + "version": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-is": { + "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dev": true + }, + "typescript": { + "version": "https://registry.npmjs.org/typescript/-/typescript-2.2.2.tgz", + "integrity": "sha1-YGAiUIR5tV/6NotY/uljoD39eww=", + "dev": true + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.19.tgz", + "integrity": "sha1-qx3+KhcTYbgfqf+zODRh6jhFV+0=", + "dev": true + }, + "uglify-to-browserify": { + "version": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "ultron": { + "version": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, + "uniq": { + "version": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqid": { + "version": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", + "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", + "dev": true + }, + "uniqs": { + "version": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unique-string": { + "version": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true + }, + "unpipe": { + "version": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unzip-response": { + "version": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "update-notifier": { + "version": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.2.0.tgz", + "integrity": "sha1-G1g3z5DAc22IYncytmHBOPht5y8=", + "dev": true + }, + "upper-case": { + "version": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "url": { + "version": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-loader": { + "version": "https://registry.npmjs.org/url-loader/-/url-loader-0.5.9.tgz", + "integrity": "sha1-zI/qgse5Bud3cBklCGnlaemVwpU=", + "dev": true + }, + "url-parse": { + "version": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz", + "integrity": "sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=", + "dev": true, + "dependencies": { + "querystringify": { + "version": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", + "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", + "dev": true + } + } + }, + "url-parse-lax": { + "version": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true + }, + "user-home": { + "version": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "useragent": { + "version": "https://registry.npmjs.org/useragent/-/useragent-2.1.13.tgz", + "integrity": "sha1-u6Q+iqJNXOuDwpN0c+EC4h33TBA=", + "dev": true, + "dependencies": { + "lru-cache": { + "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", + "dev": true + } + } + }, + "util": { + "version": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utila": { + "version": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", + "dev": true + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=", + "dev": true + }, + "v8flags": { + "version": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true + }, + "validate-npm-package-license": { + "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true + }, + "vary": { + "version": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=", + "dev": true + }, + "vendors": { + "version": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", + "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", + "dev": true + }, + "verror": { + "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true + }, + "vlq": { + "version": "https://registry.npmjs.org/vlq/-/vlq-0.2.2.tgz", + "integrity": "sha1-4xbVJXtAuGu0PLjV/qXX9U1rDKE=", + "dev": true + }, + "vm-browserify": { + "version": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true + }, + "void-elements": { + "version": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "walk-sync": { + "version": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.3.2.tgz", + "integrity": "sha1-SCcoCvxC0OA1NnxKTjHurA0Tb3U=", + "dev": true + }, + "watchpack": { + "version": "https://registry.npmjs.org/watchpack/-/watchpack-1.3.1.tgz", + "integrity": "sha1-fYaTkHsozmAT5/NhCqKhrPB9rYc=", + "dev": true + }, + "wbuf": { + "version": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", + "integrity": "sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=", + "dev": true + }, + "webdriver-js-extender": { + "version": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-1.0.0.tgz", + "integrity": "sha1-gcUzqeM9W/tZe05j4s2yW1R3dRU=", + "dev": true, + "dependencies": { + "adm-zip": { + "version": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", + "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", + "dev": true + }, + "sax": { + "version": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", + "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=", + "dev": true + }, + "selenium-webdriver": { + "version": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz", + "integrity": "sha1-0p/1qVff8aG0ncRXdW5OS/vc4IU=", + "dev": true + }, + "tmp": { + "version": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", + "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=", + "dev": true + }, + "xml2js": { + "version": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.4.tgz", + "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", + "dev": true + } + } + }, + "webpack": { + "version": "https://registry.npmjs.org/webpack/-/webpack-2.2.1.tgz", + "integrity": "sha1-e7HXKuIIfdGkr1Jq/sFe7RfdpHU=", + "dev": true, + "dependencies": { + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true + }, + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "cliui": { + "version": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "loader-utils": { + "version": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "dependencies": { + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true + } + } + }, + "webpack-sources": { + "version": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.1.5.tgz", + "integrity": "sha1-qh86vw8NdNtxEcQOUAuE+WZkB1A=", + "dev": true + }, + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + } + } + }, + "webpack-dev-middleware": { + "version": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz", + "integrity": "sha1-CWkdCXOjCtH4Ksc6EuIIfwpHVPk=", + "dev": true + }, + "webpack-dev-server": { + "version": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.4.5.tgz", + "integrity": "sha1-MThM6BE2vhCAtLTN4OubkOVO5s8=", + "dev": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true + }, + "yargs-parser": { + "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true + } + } + }, + "webpack-merge": { + "version": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-2.6.1.tgz", + "integrity": "sha1-8dgB0sXTn4P/7J8RkkCz476ZShw=", + "dev": true + }, + "webpack-sources": { + "version": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz", + "integrity": "sha1-xzVkNqTRMSO+LiQmoF0drZy+Zc8=", + "dev": true, + "dependencies": { + "source-list-map": { + "version": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha1-qqR0A/eyRakvvJfqCPJQ1gh+0IU=", + "dev": true + } + } + }, + "websocket-driver": { + "version": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true + }, + "websocket-extensions": { + "version": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=", + "dev": true + }, + "when": { + "version": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "whet.extend": { + "version": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "dev": true + }, + "which": { + "version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true + }, + "which-module": { + "version": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wide-align": { + "version": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + } + } + }, + "widest-line": { + "version": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", + "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + } + } + }, + "window-size": { + "version": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true + } + } + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.1.0.tgz", + "integrity": "sha1-F2n0tVHu3OQZ8FBd6uLiZ2NULTc=", + "dev": true + }, + "ws": { + "version": "https://registry.npmjs.org/ws/-/ws-1.1.1.tgz", + "integrity": "sha1-CC3bbGQehdS7RR8D1S8G6r2x8Bg=", + "dev": true + }, + "wtf-8": { + "version": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", + "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", + "dev": true + }, + "xdg-basedir": { + "version": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, + "xml-char-classes": { + "version": "https://registry.npmjs.org/xml-char-classes/-/xml-char-classes-1.0.0.tgz", + "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=", + "dev": true + }, + "xml2js": { + "version": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", + "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "dev": true + }, + "xmlbuilder": { + "version": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "dev": true + }, + "xmldom": { + "version": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "optional": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "optional": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "optional": true + } + } + }, + "yargs-parser": { + "version": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "optional": true, + "dependencies": { + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "optional": true + } + } + }, + "yeast": { + "version": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "https://registry.npmjs.org/yn/-/yn-1.3.0.tgz", + "integrity": "sha1-GwgSq7jYBdSJZvjfOF3J2syaGdg=", + "dev": true + }, + "zone.js": { + "version": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.12.tgz", + "integrity": "sha1-hv9QU8mK7CkaC/S7rFAdaUoFz7s=" + } + } +} diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 000000000..7ed9e1204 --- /dev/null +++ b/admin/package.json @@ -0,0 +1,50 @@ +{ + "name": "admin", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/common": "^4.0.0", + "@angular/compiler": "^4.0.0", + "@angular/core": "^4.0.0", + "@angular/forms": "^4.0.0", + "@angular/http": "^4.0.0", + "@angular/platform-browser": "^4.0.0", + "@angular/platform-browser-dynamic": "^4.0.0", + "@angular/router": "^4.0.0", + "angular-tree-component": "^3.6.0", + "bootstrap": "^3.3.7", + "bootstrap-vertical-tabs": "^1.2.2", + "core-js": "^2.4.1", + "ngx-bootstrap": "^1.7.1", + "rxjs": "^5.1.0", + "zone.js": "^0.8.4" + }, + "devDependencies": { + "@angular/cli": "1.0.3", + "@angular/compiler-cli": "^4.0.0", + "@types/jasmine": "2.5.38", + "@types/node": "~6.0.60", + "codelyzer": "~2.0.0", + "jasmine-core": "~2.5.2", + "jasmine-spec-reporter": "~3.2.0", + "karma": "~1.4.1", + "karma-chrome-launcher": "~2.1.1", + "karma-cli": "~1.0.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-coverage-istanbul-reporter": "^0.2.0", + "protractor": "~5.1.0", + "ts-node": "~2.0.0", + "tslint": "~4.5.0", + "typescript": "~2.2.0" + } +} diff --git a/admin/protractor.conf.js b/admin/protractor.conf.js new file mode 100644 index 000000000..1c5e1e5a4 --- /dev/null +++ b/admin/protractor.conf.js @@ -0,0 +1,30 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/admin/src/app/app-routing.module.ts b/admin/src/app/app-routing.module.ts new file mode 100644 index 000000000..397762be2 --- /dev/null +++ b/admin/src/app/app-routing.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AppComponent } from './app.component'; +import { WorkbasketadministrationComponent } from './workbasketadministration/workbasketadministration.component'; +import { CategoriesadministrationComponent } from './categoriesadministration/categoriesadministration.component'; + +const appRoutes: Routes = [ + { + path: 'workbaskets', + component: WorkbasketadministrationComponent + }, + { + path: 'workbaskets/:id', + component: WorkbasketadministrationComponent + }, + { + path: 'categories', + component: CategoriesadministrationComponent + }, + { + path: '', + redirectTo: 'workbaskets', + pathMatch: 'full' + }, +]; +@NgModule({ + imports: [ + RouterModule.forRoot( + appRoutes + ) + ], + exports: [ + RouterModule + ] +}) +export class AppRoutingModule { } diff --git a/admin/src/app/app.component.css b/admin/src/app/app.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/app.component.html b/admin/src/app/app.component.html new file mode 100644 index 000000000..6266ab49f --- /dev/null +++ b/admin/src/app/app.component.html @@ -0,0 +1,20 @@ + +
+ + +
diff --git a/admin/src/app/app.component.spec.ts b/admin/src/app/app.component.spec.ts new file mode 100644 index 000000000..c740bcd74 --- /dev/null +++ b/admin/src/app/app.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + 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 works!'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app works!'); + })); + + 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('app works!'); + })); +}); diff --git a/admin/src/app/app.component.ts b/admin/src/app/app.component.ts new file mode 100644 index 000000000..df1e7f589 --- /dev/null +++ b/admin/src/app/app.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { environment } from '../environments/environment'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + title = 'Taskana Administration'; + + monitorUrl: string = environment.taskanaMonitorUrl; + workplaceUrl: string = environment.taskanaWorkplaceUrl; +} diff --git a/admin/src/app/app.module.ts b/admin/src/app/app.module.ts new file mode 100644 index 000000000..0e3be14c3 --- /dev/null +++ b/admin/src/app/app.module.ts @@ -0,0 +1,48 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule, JsonpModule } from '@angular/http'; +import { AppComponent } from './app.component'; +import { TabsModule } from 'ngx-bootstrap/tabs'; +import { TreeModule } from 'angular-tree-component'; +import { WorkbasketlistComponent } from './workbasketlist/workbasketlist.component'; +import { WorkbasketeditorComponent } from './workbasketeditor/workbasketeditor.component'; +import { CategorieslistComponent } from './categorieslist/categorieslist.component'; +import { CategoriestreeComponent } from './categoriestree/categoriestree.component'; +import { CategoryeditorComponent } from './categoryeditor/categoryeditor.component'; +import { CategoriesadministrationComponent } from './categoriesadministration/categoriesadministration.component'; +import { WorkbasketadministrationComponent } from './workbasketadministration/workbasketadministration.component'; +import { WorkbasketAuthorizationComponent } from './workbasket-authorization/workbasket-authorization.component'; +import { WorkbasketDetailsComponent } from './workbasket-details/workbasket-details.component'; +import { WorkbasketDistributiontargetsComponent } from './workbasket-distributiontargets/workbasket-distributiontargets.component'; +import { AppRoutingModule } from './app-routing.module'; +import { AlertModule } from 'ngx-bootstrap'; + +@NgModule({ + declarations: [ + AppComponent, + WorkbasketlistComponent, + WorkbasketeditorComponent, + CategorieslistComponent, + CategoriestreeComponent, + CategoryeditorComponent, + CategoriesadministrationComponent, + WorkbasketadministrationComponent, + WorkbasketAuthorizationComponent, + WorkbasketDetailsComponent, + WorkbasketDistributiontargetsComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule, + JsonpModule, + TabsModule.forRoot(), + TreeModule, + AppRoutingModule, + AlertModule.forRoot() + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/admin/src/app/categoriesadministration/categoriesadministration.component.css b/admin/src/app/categoriesadministration/categoriesadministration.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/categoriesadministration/categoriesadministration.component.html b/admin/src/app/categoriesadministration/categoriesadministration.component.html new file mode 100644 index 000000000..5425f4bc2 --- /dev/null +++ b/admin/src/app/categoriesadministration/categoriesadministration.component.html @@ -0,0 +1,6 @@ +
+ +
+
+ +
\ No newline at end of file diff --git a/admin/src/app/categoriesadministration/categoriesadministration.component.spec.ts b/admin/src/app/categoriesadministration/categoriesadministration.component.spec.ts new file mode 100644 index 000000000..c2ac5f1d8 --- /dev/null +++ b/admin/src/app/categoriesadministration/categoriesadministration.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CategoriesadministrationComponent } from './categoriesadministration.component'; + +describe('CategoriesadministrattionComponent', () => { + let component: CategoriesadministrationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CategoriesadministrationComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CategoriesadministrationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/categoriesadministration/categoriesadministration.component.ts b/admin/src/app/categoriesadministration/categoriesadministration.component.ts new file mode 100644 index 000000000..119572eef --- /dev/null +++ b/admin/src/app/categoriesadministration/categoriesadministration.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'app-categoriesadministration', + templateUrl: './categoriesadministration.component.html', + styleUrls: ['./categoriesadministration.component.css'] +}) +export class CategoriesadministrationComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + + @Input() + categorySelected: any; + + onCategorySelected(arg) { + console.log("Event angekommen: ",arg); + this.categorySelected = arg; + } + +} diff --git a/admin/src/app/categoriesadministration/category.ts b/admin/src/app/categoriesadministration/category.ts new file mode 100644 index 000000000..6a408d45b --- /dev/null +++ b/admin/src/app/categoriesadministration/category.ts @@ -0,0 +1,8 @@ +export class Category { + constructor( + public id: string, + public name: string, + public description: string, + public priorty: number, + public serviceLevel: string) { } +} \ No newline at end of file diff --git a/admin/src/app/categorieslist/categorieslist.component.css b/admin/src/app/categorieslist/categorieslist.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/categorieslist/categorieslist.component.html b/admin/src/app/categorieslist/categorieslist.component.html new file mode 100644 index 000000000..17ee98d02 --- /dev/null +++ b/admin/src/app/categorieslist/categorieslist.component.html @@ -0,0 +1,12 @@ + + + + + + + + + + + +
IDNameDescription
{{ category.id }}{{ category.name }}{{ category.description }}
diff --git a/admin/src/app/categorieslist/categorieslist.component.spec.ts b/admin/src/app/categorieslist/categorieslist.component.spec.ts new file mode 100644 index 000000000..bfee39c75 --- /dev/null +++ b/admin/src/app/categorieslist/categorieslist.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CategorieslistComponent } from './categorieslist.component'; + +describe('CategorieslistComponent', () => { + let component: CategorieslistComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CategorieslistComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CategorieslistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/categorieslist/categorieslist.component.ts b/admin/src/app/categorieslist/categorieslist.component.ts new file mode 100644 index 000000000..1895bc5cd --- /dev/null +++ b/admin/src/app/categorieslist/categorieslist.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-categorieslist', + templateUrl: './categorieslist.component.html', + styleUrls: ['./categorieslist.component.css'] +}) +export class CategorieslistComponent implements OnInit { + + categories = [ { + "id": "1", + "name": "Category 1", + "description": "Das ist die erste Business Category." + }, + { + "id": "2", + "name": "Category 2", + "description": "Das ist die erste Business Category." + }, + { + "id": "3", + "name": "Category 3", + "description": "Das ist die erste Business Category." + } + ]; + + constructor() { } + + ngOnInit() {} + +} diff --git a/admin/src/app/categoriestree/categoriestree.component.css b/admin/src/app/categoriestree/categoriestree.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/categoriestree/categoriestree.component.html b/admin/src/app/categoriestree/categoriestree.component.html new file mode 100644 index 000000000..cfd223391 --- /dev/null +++ b/admin/src/app/categoriestree/categoriestree.component.html @@ -0,0 +1,3 @@ + + diff --git a/admin/src/app/categoriestree/categoriestree.component.spec.ts b/admin/src/app/categoriestree/categoriestree.component.spec.ts new file mode 100644 index 000000000..713cfbc76 --- /dev/null +++ b/admin/src/app/categoriestree/categoriestree.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CategoriestreeComponent } from './categoriestree.component'; + +describe('CategoriestreeComponent', () => { + let component: CategoriestreeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CategoriestreeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CategoriestreeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/categoriestree/categoriestree.component.ts b/admin/src/app/categoriestree/categoriestree.component.ts new file mode 100644 index 000000000..2d7ea181a --- /dev/null +++ b/admin/src/app/categoriestree/categoriestree.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit, EventEmitter, Output } from '@angular/core'; +import { Category } from '../categoriesadministration/category'; +import { CategoryService } from '../services/category.service'; + +@Component({ + selector: 'app-categoriestree', + templateUrl: './categoriestree.component.html', + styleUrls: ['./categoriestree.component.css'], + providers: [CategoryService] +}) +export class CategoriestreeComponent implements OnInit { + + categories: Category[]; + errorMessage: string; + + nodes = []; + +/* + nodes = [ + { + id: '1', + name: 'Category 1', + children: [ + { id: '2', name: 'Category 2' }, + { id: '3', name: 'Category 3' } + ] + }, + { + id: '4', + name: 'Category 4', + children: [ + { id: '5', name: 'Category 5' }, + { + id: '6', + name: 'Category 6', + children: [ + { id: '7', name: 'Category 7' } + ] + } + ] + } + ]; +*/ + + + constructor(private categoryService: CategoryService) { + } + + ngOnInit() { + this.getCategories(); + } + + @Output() + categorySelected = new EventEmitter(); + + onCategorySelected(arg) { + console.log("Selected: ", arg); + var category: Category = arg.node.data; + this.categorySelected.next(category); + } + + getCategories() { + console.log("Going to load categories..."); + this.categoryService.getCategories() + .subscribe(c => { + this.categories = c; + console.log("RESPONSE: ", c); + this.nodes = c; + if (this.categories != null && this.categories.length > 0) { + // this.nodes = []; + this.categories.forEach(category => { + console.log("Geladene Category: ", category); +// let count = 1; +// this.nodes.push({'1', 'NAME'}); +// count++; + }); + } + }); + + + + // this.autoCompleteData.push(workbasket.name); + // categories => this.categories = heroes, + // error => this.errorMessage = error); + } + +} diff --git a/admin/src/app/categoryeditor/categoryeditor.component.css b/admin/src/app/categoryeditor/categoryeditor.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/categoryeditor/categoryeditor.component.html b/admin/src/app/categoryeditor/categoryeditor.component.html new file mode 100644 index 000000000..258f66980 --- /dev/null +++ b/admin/src/app/categoryeditor/categoryeditor.component.html @@ -0,0 +1,38 @@ +

Category Details of {{categorySelected?.name}} (ID: {{categorySelected?.id}})

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
diff --git a/admin/src/app/categoryeditor/categoryeditor.component.spec.ts b/admin/src/app/categoryeditor/categoryeditor.component.spec.ts new file mode 100644 index 000000000..6966585b4 --- /dev/null +++ b/admin/src/app/categoryeditor/categoryeditor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CategoryeditorComponent } from './categoryeditor.component'; + +describe('CategoryeditorComponent', () => { + let component: CategoryeditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CategoryeditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CategoryeditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/categoryeditor/categoryeditor.component.ts b/admin/src/app/categoryeditor/categoryeditor.component.ts new file mode 100644 index 000000000..af3aedf67 --- /dev/null +++ b/admin/src/app/categoryeditor/categoryeditor.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Category } from '../categoriesadministration/category'; + +@Component({ + selector: 'app-categoryeditor', + templateUrl: './categoryeditor.component.html', + styleUrls: ['./categoryeditor.component.css'] +}) +export class CategoryeditorComponent implements OnInit { + + constructor() { } + + ngOnInit() { + this.categorySelected = new Category('', '', '', 0,''); + } + + @Input() + categorySelected: Category; + +} diff --git a/admin/src/app/model/workbasket-authorization.ts b/admin/src/app/model/workbasket-authorization.ts new file mode 100644 index 000000000..cb9e57948 --- /dev/null +++ b/admin/src/app/model/workbasket-authorization.ts @@ -0,0 +1,21 @@ +export class WorkbasketAuthorization { + id: string; + workbasketId: string; + userId: string; + groupId: string; + read: boolean; + open: boolean; + append: boolean; + transfer: boolean; + distribute: boolean; + + constructor(id: string, + workbasketId: string, + userId: string, + groupId: string, + read: boolean, + open: boolean, + append: boolean, + transfer: boolean, + distribute: boolean) { } +} \ No newline at end of file diff --git a/admin/src/app/model/workbasket.ts b/admin/src/app/model/workbasket.ts new file mode 100644 index 000000000..92efee025 --- /dev/null +++ b/admin/src/app/model/workbasket.ts @@ -0,0 +1,11 @@ +export class Workbasket { + constructor( + public id: string, + public tenantId: string, + public created: string, + public modified: string, + public name: string, + public description: string, + public owner: string, + public distributionTargets: [string]) { } +} \ No newline at end of file diff --git a/admin/src/app/services/category.service.spec.ts b/admin/src/app/services/category.service.spec.ts new file mode 100644 index 000000000..34cdebcd7 --- /dev/null +++ b/admin/src/app/services/category.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { CategoryService } from './category.service'; + +describe('CategoryService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [CategoryService] + }); + }); + + it('should be created', inject([CategoryService], (service: CategoryService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/admin/src/app/services/category.service.ts b/admin/src/app/services/category.service.ts new file mode 100644 index 000000000..835bf761d --- /dev/null +++ b/admin/src/app/services/category.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { RequestOptions, Headers, Http, Response } from '@angular/http'; +import { environment } from '../../environments/environment'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/map'; + +import { Category } from '../categoriesadministration/category'; + +@Injectable() +export class CategoryService { + private categoryServiceUrl = environment.taskanaRestUrl + '/v1/categories'; // URL to web API + constructor(private http: Http) { } + getCategories(): Observable { + return this.http.get(this.categoryServiceUrl, this.createAuthorizationHeader()) + .map(this.extractData) + .catch(this.handleError); + } + private extractData(res: Response) { + let body = res.json(); + console.log("Body: ", body); + return body; + } + private handleError(error: Response | any) { + // In a real world app, you might use a remote logging infrastructure + let errMsg: string; + if (error instanceof Response) { + const body = error.json() || ''; + const err = body.error || JSON.stringify(body); + errMsg = `${error.status} - ${error.statusText || ''} ${err}`; + } else { + errMsg = error.message ? error.message : error.toString(); + } + console.error(errMsg); + return Observable.throw(errMsg); + } + + private createAuthorizationHeader() { + let headers: Headers = new Headers(); + headers.append("Authorization", "Basic TWF4OnRlc3Q="); + + return new RequestOptions({ headers: headers }); + } +} diff --git a/admin/src/app/services/workbasketservice.service.spec.ts b/admin/src/app/services/workbasketservice.service.spec.ts new file mode 100644 index 000000000..ea74a5a5c --- /dev/null +++ b/admin/src/app/services/workbasketservice.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { WorkbasketserviceService } from './workbasketservice.service'; + +describe('WorkbasketserviceService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [WorkbasketserviceService] + }); + }); + + it('should be created', inject([WorkbasketserviceService], (service: WorkbasketserviceService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/admin/src/app/services/workbasketservice.service.ts b/admin/src/app/services/workbasketservice.service.ts new file mode 100644 index 000000000..295df817a --- /dev/null +++ b/admin/src/app/services/workbasketservice.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { RequestOptions, Headers, Http, Response } from '@angular/http'; +import { Workbasket } from '../model/workbasket'; +import { WorkbasketAuthorization } from '../model/workbasket-authorization'; +import { environment } from '../../environments/environment'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/Rx'; + +@Injectable() +export class WorkbasketserviceService { + + constructor(private http: Http) { } + + getAllWorkBaskets(): Observable { + return this.http.get(environment.taskanaRestUrl + "/v1/workbaskets", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + createWorkbasket(workbasket: Workbasket): Observable { + return this.http.post(environment.taskanaRestUrl + "/v1/workbaskets", workbasket, this.createAuthorizationHeader()) + .map(res => res.json()); + } + + deleteWorkbasket(id: string) { + return this.http.delete(environment.taskanaRestUrl + "/v1/workbaskets/" + id, this.createAuthorizationHeader()) + .map(res => res.json()); + } + + updateWorkbasket(workbasket: Workbasket): Observable { + return this.http.put(environment.taskanaRestUrl + "/v1/workbaskets/" + workbasket.id, workbasket, this.createAuthorizationHeader()) + .map(res => res.json()); + } + + getAllWorkBasketAuthorizations(id: String): Observable { + return this.http.get(environment.taskanaRestUrl + "/v1/workbaskets/" + id + "/authorizations", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + createWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable { + return this.http.post(environment.taskanaRestUrl + "/v1/workbaskets/authorizations", workbasketAuthorization, this.createAuthorizationHeader()) + .map(res => res.json()); + } + + updateWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable { + return this.http.put(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, workbasketAuthorization, this.createAuthorizationHeader()) + .map(res => res.json()); + } + + deleteWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization) { + return this.http.delete(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, this.createAuthorizationHeader()); + } + + private createAuthorizationHeader() { + let headers: Headers = new Headers(); + headers.append("Authorization", "Basic TWF4OnRlc3Q="); + headers.append("content-type", "application/json"); + + return new RequestOptions({ headers: headers }); + } +} diff --git a/admin/src/app/workbasket-authorization/workbasket-authorization.component.css b/admin/src/app/workbasket-authorization/workbasket-authorization.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/workbasket-authorization/workbasket-authorization.component.html b/admin/src/app/workbasket-authorization/workbasket-authorization.component.html new file mode 100644 index 000000000..d93b9a37c --- /dev/null +++ b/admin/src/app/workbasket-authorization/workbasket-authorization.component.html @@ -0,0 +1,106 @@ +

Authorizations

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDUserIDGroupIDREADOPENAPPENDTRANSFERDISTRIBUTEActions
+ + + + + + + + + + + + + + + + +
{{ workbasketAuthorization.id }} + + {{ workbasketAuthorization.userId }} + + {{ workbasketAuthorization.groupId }} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/admin/src/app/workbasket-authorization/workbasket-authorization.component.spec.ts b/admin/src/app/workbasket-authorization/workbasket-authorization.component.spec.ts new file mode 100644 index 000000000..b0a0824a0 --- /dev/null +++ b/admin/src/app/workbasket-authorization/workbasket-authorization.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketAuthorizationComponent } from './workbasket-authorization.component'; + +describe('WorkbasketAuthorizationComponent', () => { + let component: WorkbasketAuthorizationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketAuthorizationComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketAuthorizationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/workbasket-authorization/workbasket-authorization.component.ts b/admin/src/app/workbasket-authorization/workbasket-authorization.component.ts new file mode 100644 index 000000000..d72c252e0 --- /dev/null +++ b/admin/src/app/workbasket-authorization/workbasket-authorization.component.ts @@ -0,0 +1,101 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; +import { WorkbasketserviceService } from '../services/workbasketservice.service'; +import { WorkbasketAuthorization } from '../model/workbasket-authorization'; +import { Workbasket } from '../model/workbasket'; + +@Component({ + selector: 'app-workbasket-authorization', + templateUrl: './workbasket-authorization.component.html', + styleUrls: ['./workbasket-authorization.component.css'], + providers: [WorkbasketserviceService] +}) +export class WorkbasketAuthorizationComponent implements OnInit { + + @Input() + workbasket: Workbasket; + + workbasketAuthorization: WorkbasketAuthorization = this.getEmptyObject(); + selected: WorkbasketAuthorization = this.getEmptyObject(); + editing: WorkbasketAuthorization = this.getEmptyObject(); + isEditing: boolean = false; + + constructor(private service: WorkbasketserviceService, private route: ActivatedRoute) { } + + workbasketAuthorizations: WorkbasketAuthorization[]; + + ngOnInit() { + this.route.params.switchMap((params: Params) => this.service.getAllWorkBasketAuthorizations(params['id'])) + .subscribe(resultList => { + this.workbasketAuthorizations = resultList; + }); + } + + getEmptyObject() { + return new WorkbasketAuthorization("", "", "", "", false, false, false, false, false); + } + + onDelete(workbasket: WorkbasketAuthorization) { + this.service.deleteWorkBasketAuthorization(workbasket).subscribe(result => { + var index = this.workbasketAuthorizations.indexOf(workbasket); + if (index > -1) { + this.workbasketAuthorizations.splice(index, 1); + } + }); + } + + onAdd() { + console.log(this.workbasketAuthorization); + this.workbasketAuthorization.workbasketId = this.workbasket.id; + this.service.createWorkBasketAuthorization(this.workbasketAuthorization).subscribe(result => { + this.workbasketAuthorizations.push(result); + this.onClear(); + }); + } + + onEdit(workbasketAuthorizations: WorkbasketAuthorization) { + this.editing = { ...workbasketAuthorizations }; + this.isEditing = true; + } + + onSelect(workbasketAuthorizations: WorkbasketAuthorization) { + if (!this.isEditing) { + this.selected = workbasketAuthorizations; + } + } + + onClear() { + this.workbasketAuthorization.id = ""; + this.workbasketAuthorization.workbasketId = ""; + this.workbasketAuthorization.userId = ""; + this.workbasketAuthorization.groupId = ""; + this.workbasketAuthorization.read = false; + this.workbasketAuthorization.open = false; + this.workbasketAuthorization.append = false; + this.workbasketAuthorization.transfer = false; + this.workbasketAuthorization.distribute = false; + } + + onSave() { + if (this.isEditing) { + this.service.updateWorkBasketAuthorization(this.editing).subscribe(result => { + this.selected.id = result.id; + this.selected.workbasketId = result.workbasketId; + this.selected.userId = result.userId; + this.selected.groupId = result.groupId; + this.selected.read = result.read; + this.selected.open = result.open; + this.selected.append = result.append; + this.selected.transfer = result.transfer; + this.selected.distribute = result.distribute; + }); + } + this.isEditing = false; + this.editing = this.getEmptyObject(); + } + + onCancel() { + this.editing = this.getEmptyObject(); + this.isEditing = false; + } +} diff --git a/admin/src/app/workbasket-details/workbasket-details.component.css b/admin/src/app/workbasket-details/workbasket-details.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/workbasket-details/workbasket-details.component.html b/admin/src/app/workbasket-details/workbasket-details.component.html new file mode 100644 index 000000000..f432201c1 --- /dev/null +++ b/admin/src/app/workbasket-details/workbasket-details.component.html @@ -0,0 +1,32 @@ +
+
+

Details

+
+
+ + +
+
+
+
Id
+
{{ workbasketClone.id }}
+
Tenant Id
+
{{ workbasketClone.tenantId }}
+
+
Created Date
+
{{ workbasketClone.created | date: 'dd/MM/yyyy HH:mm' }}
+
Modified Date
+
{{ workbasketClone.modified | date: 'dd/MM/yyyy HH:mm' }}
+
Name
+
{{ workbasketClone.name }}
+
+
Owner
+
{{ workbasketClone.owner }}
+
+
Description
+
{{ workbasketClone.description }}
+
+
\ No newline at end of file diff --git a/admin/src/app/workbasket-details/workbasket-details.component.spec.ts b/admin/src/app/workbasket-details/workbasket-details.component.spec.ts new file mode 100644 index 000000000..65b713e7f --- /dev/null +++ b/admin/src/app/workbasket-details/workbasket-details.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketDetailsComponent } from './workbasket-details.component'; + +describe('WorkbasketDetailsComponent', () => { + let component: WorkbasketDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketDetailsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/workbasket-details/workbasket-details.component.ts b/admin/src/app/workbasket-details/workbasket-details.component.ts new file mode 100644 index 000000000..49d5fadc4 --- /dev/null +++ b/admin/src/app/workbasket-details/workbasket-details.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Workbasket } from '../model/workbasket'; +import { WorkbasketserviceService } from '../services/workbasketservice.service'; + +@Component({ + selector: 'app-workbasket-details', + templateUrl: './workbasket-details.component.html', + styleUrls: ['./workbasket-details.component.css'] +}) +export class WorkbasketDetailsComponent implements OnInit { + + @Input() + workbasket: Workbasket; + workbasketClone: Workbasket; + + allWorkbasket: Workbasket[]; + editMode: boolean = false; + + @Output() + onSaved = new EventEmitter(); + + constructor(private service: WorkbasketserviceService) { } + + ngOnInit() { + this.workbasketClone = { ...this.workbasket }; + } + + ngOnChanges() { + this.workbasketClone = { ...this.workbasket }; + this.editMode = false; + } + + onEdit() { + this.editMode = true; + } + + onSave() { + this.onSaved.emit(this.workbasketClone); + this.editMode = false; + } +} diff --git a/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.css b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.html b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.html new file mode 100644 index 000000000..f2f64973d --- /dev/null +++ b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.html @@ -0,0 +1,34 @@ +

Distribution Targets

+
+ {{ alert.msg }} +
+
+ + + + + + + + + +
All WorkbasketsActions
{{ workbasket.name }} + +
+
+
+ + + + + + + + + +
Selected WorkbasketsActions
{{ resolveName(workbasket) }} + +
+
\ No newline at end of file diff --git a/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.spec.ts b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.spec.ts new file mode 100644 index 000000000..74c0866d2 --- /dev/null +++ b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketDistributiontargetsComponent } from './workbasket-distributiontargets.component'; + +describe('WorkbasketDistributiontargetsComponent', () => { + let component: WorkbasketDistributiontargetsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketDistributiontargetsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketDistributiontargetsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.ts b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.ts new file mode 100644 index 000000000..06cb79cef --- /dev/null +++ b/admin/src/app/workbasket-distributiontargets/workbasket-distributiontargets.component.ts @@ -0,0 +1,107 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Workbasket } from '../model/workbasket'; +import { WorkbasketserviceService } from '../services/workbasketservice.service' +import { Observable } from 'rxjs/Observable'; + +@Component({ + selector: 'app-workbasket-distributiontargets', + templateUrl: './workbasket-distributiontargets.component.html', + styleUrls: ['./workbasket-distributiontargets.component.css'] +}) +export class WorkbasketDistributiontargetsComponent implements OnInit { + + @Input() + workbasket: Workbasket; + workbaskets: Workbasket[]; + public alerts: any = []; + + constructor(private service: WorkbasketserviceService) { } + + ngOnInit() { + this.prepareData(); + } + + ngOnChange() { + this.prepareData(); + } + + prepareData() { + this.service.getAllWorkBaskets().subscribe(resultList => { + this.workbaskets = resultList; + }); + } + + onAdd(w: Workbasket) { + if (this.workbasket.distributionTargets.length > 0) { + let found: boolean = false; + for (var i = 0, len = this.workbasket.distributionTargets.length; i < len; i++) { + if (this.workbasket.distributionTargets[i] === w.id) { + found = true; + break; + } + } + + if (!found) { + this.onSaved(w, true); + } else { + this.alerts = [{ + type: "danger", + msg: "This workbasket is already mapped!" + }]; + } + } else { + this.onSaved(w, true); + } + } + + onDelete(id: string) { + this.onSaved(this.resolveObject(id), false); + } + + // get workbasket name + resolveName(id: string): any { + if (this.workbaskets != null) { + return this.workbaskets.filter(item => item.id === id)[0].name; + } + } + + // create an Workbasket + resolveObject(id: string): any { + if (this.workbaskets != null) { + return this.workbaskets.filter(item => item.id === id)[0]; + } + } + + onSaved(w: Workbasket, isUpdate: boolean) { + if (w != null) { + // add changes + if (isUpdate) { + this.workbasket.distributionTargets.push(w.id); + } else { + let index = this.workbasket.distributionTargets.indexOf(w.id); + this.workbasket.distributionTargets.splice(index, 1); + } + + // try to save changes + this.service.updateWorkbasket(this.workbasket).subscribe(result => { + this.workbasket.id = result.id; + this.workbasket.name = result.name; + this.workbasket.description = result.description; + this.workbasket.owner = result.owner; + this.workbasket.tenantId = result.tenantId; + this.workbasket.distributionTargets = result.distributionTargets; + }, (err) => { + this.alerts = [{ + type: "danger", + msg: "You are not authorized." + }]; + // reset changes + if (isUpdate) { + this.workbasket.distributionTargets.pop(); + } else { + this.workbasket.distributionTargets.push(w.id); + } + }); + } + } +} diff --git a/admin/src/app/workbasketadministration/workbasketadministration.component.css b/admin/src/app/workbasketadministration/workbasketadministration.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/workbasketadministration/workbasketadministration.component.html b/admin/src/app/workbasketadministration/workbasketadministration.component.html new file mode 100644 index 000000000..b55c0457d --- /dev/null +++ b/admin/src/app/workbasketadministration/workbasketadministration.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/admin/src/app/workbasketadministration/workbasketadministration.component.spec.ts b/admin/src/app/workbasketadministration/workbasketadministration.component.spec.ts new file mode 100644 index 000000000..0a6136c5c --- /dev/null +++ b/admin/src/app/workbasketadministration/workbasketadministration.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketadministrationComponent } from './workbasketadministration.component'; + +describe('WorkbasketadministrationComponent', () => { + let component: WorkbasketadministrationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketadministrationComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketadministrationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/workbasketadministration/workbasketadministration.component.ts b/admin/src/app/workbasketadministration/workbasketadministration.component.ts new file mode 100644 index 000000000..451dedb6f --- /dev/null +++ b/admin/src/app/workbasketadministration/workbasketadministration.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit } from '@angular/core'; +import { Workbasket } from '../model/workbasket'; + +@Component({ + selector: 'app-workbasketadministration', + templateUrl: './workbasketadministration.component.html', + styleUrls: ['./workbasketadministration.component.css'] +}) +export class WorkbasketadministrationComponent implements OnInit { + selectedWorkbasket: Workbasket; + + constructor() { } + + ngOnInit() { + } + + onWorkbasketSelected(workbasket: Workbasket) { + console.log("got new selected workbasket: " + workbasket.id); + this.selectedWorkbasket = workbasket; + } + + onWorkbasketSaved(workbasket: Workbasket) { + console.log("got saved workbasket: " + workbasket); + } + +} diff --git a/admin/src/app/workbasketeditor/workbasketeditor.component.css b/admin/src/app/workbasketeditor/workbasketeditor.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/workbasketeditor/workbasketeditor.component.html b/admin/src/app/workbasketeditor/workbasketeditor.component.html new file mode 100644 index 000000000..069eecdd6 --- /dev/null +++ b/admin/src/app/workbasketeditor/workbasketeditor.component.html @@ -0,0 +1,38 @@ +

Edit Workbasket

+
+
+ +
+ +
+
+
+ +
+ +
+ The workbasket name is required +
+
+
+
+ +
+ +
+
+
+ +
+ +
+ The workbasket owner is required +
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/admin/src/app/workbasketeditor/workbasketeditor.component.spec.ts b/admin/src/app/workbasketeditor/workbasketeditor.component.spec.ts new file mode 100644 index 000000000..35a5d9996 --- /dev/null +++ b/admin/src/app/workbasketeditor/workbasketeditor.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketeditorComponent } from './workbasketeditor.component'; + +describe('WorkbasketeditorComponent', () => { + let component: WorkbasketeditorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketeditorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketeditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/workbasketeditor/workbasketeditor.component.ts b/admin/src/app/workbasketeditor/workbasketeditor.component.ts new file mode 100644 index 000000000..26fa52bc1 --- /dev/null +++ b/admin/src/app/workbasketeditor/workbasketeditor.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, EventEmitter } from '@angular/core'; +import { Workbasket } from '../model/workbasket'; + +@Component({ + selector: 'app-workbasketeditor', + inputs: ['workbasket'], + outputs: ['workbasketSaved'], + templateUrl: './workbasketeditor.component.html', + styleUrls: ['./workbasketeditor.component.css'] +}) +export class WorkbasketeditorComponent implements OnInit { + public workbasket: Workbasket; + public workbasketSaved: EventEmitter = new EventEmitter(); + + constructor() { } + + ngOnInit() { + this.workbasket = new Workbasket("", "", "", "", "", "", "", null); + } + + onSubmit() { + // TODO save values + console.log("changed " + this.workbasket.name); + this.workbasketSaved.next(this.workbasket); + } +} diff --git a/admin/src/app/workbasketlist/workbasketlist.component.css b/admin/src/app/workbasketlist/workbasketlist.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/app/workbasketlist/workbasketlist.component.html b/admin/src/app/workbasketlist/workbasketlist.component.html new file mode 100644 index 000000000..111187b23 --- /dev/null +++ b/admin/src/app/workbasketlist/workbasketlist.component.html @@ -0,0 +1,87 @@ +

Workbaskets

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDNameDescriptionOwnerActions
+ + + + + + + + +
{{ workbasket.id }} + + {{ workbasket.name }} + + {{ workbasket.description }} + + {{ workbasket.owner }} + + + + + + + +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
diff --git a/admin/src/app/workbasketlist/workbasketlist.component.spec.ts b/admin/src/app/workbasketlist/workbasketlist.component.spec.ts new file mode 100644 index 000000000..a74ae46e8 --- /dev/null +++ b/admin/src/app/workbasketlist/workbasketlist.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketlistComponent } from './workbasketlist.component'; + +describe('WorkbasketlistComponent', () => { + let component: WorkbasketlistComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketlistComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketlistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/workbasketlist/workbasketlist.component.ts b/admin/src/app/workbasketlist/workbasketlist.component.ts new file mode 100644 index 000000000..97e8f08bf --- /dev/null +++ b/admin/src/app/workbasketlist/workbasketlist.component.ts @@ -0,0 +1,115 @@ +import { Component, OnInit, EventEmitter } from '@angular/core'; +import { Workbasket } from '../model/workbasket'; +import { WorkbasketserviceService } from '../services/workbasketservice.service' +import { ActivatedRoute, Params } from '@angular/router'; + +@Component({ + selector: 'app-workbasketlist', + outputs: ['selectedWorkbasket'], + templateUrl: './workbasketlist.component.html', + styleUrls: ['./workbasketlist.component.css'], + providers: [WorkbasketserviceService] +}) +export class WorkbasketlistComponent implements OnInit { + public selectedWorkbasket: EventEmitter = new EventEmitter(); + + workbasket: Workbasket = this.getEmptyObject(); + selected: Workbasket = this.getEmptyObject(); + editing: Workbasket = this.getEmptyObject(); + isEditing: boolean = false; + + wbClicked = true; + authClicked = false; + dtClicked = false + + workbaskets = []; + + public alerts: any = []; + + constructor(private service: WorkbasketserviceService, private route: ActivatedRoute) { } + + ngOnInit() { + this.service.getAllWorkBaskets().subscribe(resultList => { + this.workbaskets = resultList; + }); + } + + onDelete(workbasket: Workbasket) { + this.service.deleteWorkbasket(workbasket.id).subscribe(result => { + var index = this.workbaskets.indexOf(workbasket); + if (index > -1) { + this.workbaskets.splice(index, 1); + } + }); + } + + onAdd() { + this.service.createWorkbasket(this.workbasket).subscribe(result => { + this.workbaskets.push(result); + this.onClear(); + }); + } + + onEdit(workbasket: Workbasket) { + this.editing = { ...workbasket }; + this.isEditing = true; + } + + onSelect(workbasket: Workbasket) { + if (!this.isEditing) { + this.selected = workbasket; + } + } + + onClear() { + this.workbasket.id = ""; + this.workbasket.name = ""; + this.workbasket.description = ""; + this.workbasket.owner = ""; + } + + onSave() { + if (this.isEditing) { + this.service.updateWorkbasket(this.editing).subscribe(result => { + this.selected.id = result.id; + this.selected.name = result.name; + this.selected.description = result.description; + this.selected.owner = result.owner; + }, (err) => { + this.alerts = [{ + type: "danger", + msg: "You are not authorized." + }] + }); + } + this.isEditing = false; + this.editing = this.getEmptyObject(); + } + + onCancel() { + this.editing = this.getEmptyObject(); + this.isEditing = false; + } + + getEmptyObject() { + return new Workbasket("", "", "", "", "", "", "", null); + } + + onClickWB() { + this.wbClicked = !this.wbClicked; + this.authClicked = false; + this.dtClicked = false; + } + + onClickAuth() { + this.authClicked = !this.authClicked; + this.wbClicked = false; + this.dtClicked = false; + } + + onClickDt() { + this.dtClicked = !this.dtClicked; + this.wbClicked = false; + this.authClicked = false; + } +} diff --git a/admin/src/assets/.gitkeep b/admin/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/environments/environment.prod.ts b/admin/src/environments/environment.prod.ts new file mode 100644 index 000000000..aa1189817 --- /dev/null +++ b/admin/src/environments/environment.prod.ts @@ -0,0 +1,6 @@ +export const environment = { + production: true, + taskanaWorkplaceUrl: 'http://taskana-workplace.mybluemix.net', + taskanaMonitorUrl: 'http://taskana-monitor.mybluemix.net', + taskanaRestUrl: 'http://taskana-rest.mybluemix.net' +}; diff --git a/admin/src/environments/environment.ts b/admin/src/environments/environment.ts new file mode 100644 index 000000000..172e0af85 --- /dev/null +++ b/admin/src/environments/environment.ts @@ -0,0 +1,11 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false, + taskanaWorkplaceUrl: 'http://localhost:4200', + taskanaMonitorUrl: 'http://localhost:4202', + taskanaRestUrl: 'http://localhost:8080' +}; diff --git a/admin/src/favicon.ico b/admin/src/favicon.ico new file mode 100644 index 000000000..8081c7cea Binary files /dev/null and b/admin/src/favicon.ico differ diff --git a/admin/src/index.html b/admin/src/index.html new file mode 100644 index 000000000..ef20c6fb1 --- /dev/null +++ b/admin/src/index.html @@ -0,0 +1,17 @@ + + + + + + Admin + + + + + + + + Loading... + + + \ No newline at end of file diff --git a/admin/src/main.ts b/admin/src/main.ts new file mode 100644 index 000000000..a9ca1caf8 --- /dev/null +++ b/admin/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/admin/src/polyfills.ts b/admin/src/polyfills.ts new file mode 100644 index 000000000..bc94e7a6d --- /dev/null +++ b/admin/src/polyfills.ts @@ -0,0 +1,72 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** 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'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/admin/src/styles.css b/admin/src/styles.css new file mode 100644 index 000000000..ffe4fb5e8 --- /dev/null +++ b/admin/src/styles.css @@ -0,0 +1,7 @@ +/* You can add global styles to this file, and also import other style files */ +.ng-valid[required], .ng-valid.required { + border-left: 5px solid #42A948; /* green */ +} +.ng-invalid:not(form) { + border-left: 5px solid #a94442; /* red */ +} \ No newline at end of file diff --git a/admin/src/test.ts b/admin/src/test.ts new file mode 100644 index 000000000..9bf72267e --- /dev/null +++ b/admin/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/admin/src/tsconfig.app.json b/admin/src/tsconfig.app.json new file mode 100644 index 000000000..5e2507db5 --- /dev/null +++ b/admin/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/admin/src/tsconfig.spec.json b/admin/src/tsconfig.spec.json new file mode 100644 index 000000000..510e3f1fd --- /dev/null +++ b/admin/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/admin/src/typings.d.ts b/admin/src/typings.d.ts new file mode 100644 index 000000000..ef5c7bd62 --- /dev/null +++ b/admin/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 000000000..a35a8ee3a --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2016", + "dom" + ] + } +} diff --git a/admin/tslint.json b/admin/tslint.json new file mode 100644 index 000000000..97adaa5e9 --- /dev/null +++ b/admin/tslint.json @@ -0,0 +1,130 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs" + ], + "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, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +} diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 000000000..3c07b903b --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,25 @@ +*/target/ +!.mvn/wrapper/maven-wrapper.jar +*.orig + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ diff --git a/lib/pom.xml b/lib/pom.xml new file mode 100644 index 000000000..dc452827b --- /dev/null +++ b/lib/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + org.taskana + taskana-parent + 0.0.1-SNAPSHOT + pom + + + 1.8 + + + + taskana-core + taskana-cdi + taskana-spring + taskana-cdi-example + taskana-spring-example + + \ No newline at end of file diff --git a/lib/readme.md b/lib/readme.md new file mode 100644 index 000000000..559bb0256 --- /dev/null +++ b/lib/readme.md @@ -0,0 +1,2 @@ +TODO +- Framework design checken \ No newline at end of file diff --git a/lib/taskana-cdi-example/.gitignore b/lib/taskana-cdi-example/.gitignore new file mode 100644 index 000000000..9ed481cb6 --- /dev/null +++ b/lib/taskana-cdi-example/.gitignore @@ -0,0 +1,2 @@ +/.apt_generated/ +/target/ diff --git a/lib/taskana-cdi-example/pom.xml b/lib/taskana-cdi-example/pom.xml new file mode 100644 index 000000000..2603667fe --- /dev/null +++ b/lib/taskana-cdi-example/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + org.taskana + taskana-cdi-example + 0.0.1-SNAPSHOT + war + + + 1.8 + 1.8 + UTF-8 + + + + + javax + javaee-api + 7.0 + provided + + + + org.taskana + taskana-core + 0.0.1-SNAPSHOT + + + + org.taskana + taskana-cdi + 0.0.1-SNAPSHOT + + + \ No newline at end of file diff --git a/lib/taskana-cdi-example/src/main/java/org/taskana/ExampleBootstrap.java b/lib/taskana-cdi-example/src/main/java/org/taskana/ExampleBootstrap.java new file mode 100644 index 000000000..7ab382a02 --- /dev/null +++ b/lib/taskana-cdi-example/src/main/java/org/taskana/ExampleBootstrap.java @@ -0,0 +1,32 @@ +package org.taskana; + +import javax.annotation.PostConstruct; +import javax.ejb.EJB; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Initialized; +import javax.enterprise.event.Observes; + +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.model.Task; + +@ApplicationScoped +public class ExampleBootstrap { + + @EJB + private TaskanaEjb taskanaEjb; + + @PostConstruct + public void init(@Observes @Initialized(ApplicationScoped.class) Object init) throws TaskNotFoundException, NotAuthorizedException { + System.out.println("---------------------------> Start App"); + Task task = taskanaEjb.getTaskService().create(new Task()); + System.out.println("---------------------------> Task started: " + task.getId()); + taskanaEjb.getTaskService().claim(task.getId(), "John Doe"); + System.out.println( + "---------------------------> Task claimed: " + + taskanaEjb.getTaskService().getTaskById(task.getId()).getOwner()); + // taskService.complete(task.getId()); + // System.out.println("---------------------------> Task completed"); + } + +} diff --git a/lib/taskana-cdi-example/src/main/java/org/taskana/TaskanaEjb.java b/lib/taskana-cdi-example/src/main/java/org/taskana/TaskanaEjb.java new file mode 100644 index 000000000..01c0b9ffb --- /dev/null +++ b/lib/taskana-cdi-example/src/main/java/org/taskana/TaskanaEjb.java @@ -0,0 +1,15 @@ +package org.taskana; + +import javax.ejb.Stateless; +import javax.inject.Inject; + +@Stateless +public class TaskanaEjb { + + @Inject + private TaskService taskService; + + public TaskService getTaskService() { + return taskService; + } +} diff --git a/lib/taskana-cdi-example/src/main/resources/taskana.properties b/lib/taskana-cdi-example/src/main/resources/taskana.properties new file mode 100644 index 000000000..a7920f7b8 --- /dev/null +++ b/lib/taskana-cdi-example/src/main/resources/taskana.properties @@ -0,0 +1 @@ +datasource.jndi=java:jboss/datasources/Taskana \ No newline at end of file diff --git a/lib/taskana-cdi-example/src/main/webapp/META-INF/MANIFEST.MF b/lib/taskana-cdi-example/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/lib/taskana-cdi-example/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/lib/taskana-cdi-example/src/main/webapp/WEB-INF/beans.xml b/lib/taskana-cdi-example/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 000000000..e69de29bb diff --git a/lib/taskana-cdi-example/src/main/webapp/WEB-INF/web.xml b/lib/taskana-cdi-example/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..a0d739cc7 --- /dev/null +++ b/lib/taskana-cdi-example/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/lib/taskana-cdi/.gitignore b/lib/taskana-cdi/.gitignore new file mode 100644 index 000000000..9ed481cb6 --- /dev/null +++ b/lib/taskana-cdi/.gitignore @@ -0,0 +1,2 @@ +/.apt_generated/ +/target/ diff --git a/lib/taskana-cdi/pom.xml b/lib/taskana-cdi/pom.xml new file mode 100644 index 000000000..3e6262bee --- /dev/null +++ b/lib/taskana-cdi/pom.xml @@ -0,0 +1,111 @@ + + 4.0.0 + org.taskana + taskana-cdi + 0.0.1-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + 2017.4.0 + 3.1.2.Final + + + + + + org.wildfly.swarm + bom + ${version.wildfly.swarm} + import + pom + + + org.jboss.arquillian + arquillian-bom + 1.1.10.Final + pom + import + + + + + + jboss + http://repository.jboss.org/nexus/content/groups/public-jboss/ + + + + + + javax + javaee-api + 7.0 + provided + + + org.taskana + taskana-core + 0.0.1-SNAPSHOT + provided + + + junit + junit + 4.12 + test + + + org.wildfly.swarm + jaxrs + test + + + org.wildfly.swarm + cdi + test + + + org.jboss.resteasy + resteasy-client + ${version.resteasy} + test + + + com.h2database + h2 + 1.4.194 + test + + + + org.wildfly.swarm + arquillian + test + + + + org.jboss.arquillian.junit + arquillian-junit-container + test + + + + + + org.wildfly.swarm + wildfly-swarm-plugin + ${version.wildfly.swarm} + + + + package + + + + + + + \ No newline at end of file diff --git a/lib/taskana-cdi/src/main/java/org/taskana/TaskanaProducers.java b/lib/taskana-cdi/src/main/java/org/taskana/TaskanaProducers.java new file mode 100644 index 000000000..278a3d5ff --- /dev/null +++ b/lib/taskana-cdi/src/main/java/org/taskana/TaskanaProducers.java @@ -0,0 +1,64 @@ +package org.taskana; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.Properties; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.taskana.configuration.TaskanaEngineConfiguration; + +@ApplicationScoped +public class TaskanaProducers { + + private static final Logger logger = LoggerFactory.getLogger(TaskanaProducers.class); + + private static final String TASKANA_PROPERTIES = "taskana.properties"; + + @Inject + private TaskanaEngine taskanaEngine; + + private TaskanaEngineConfiguration taskanaEngineConfiguration; + + @PostConstruct + public void init() { + // Load Properties and get Datasource via Context + // Load DataSource via Container + Context ctx; + DataSource dataSource; + ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + try (InputStream propertyStream = classloader.getResourceAsStream(TASKANA_PROPERTIES)) { + Properties properties = new Properties(); + ctx = new InitialContext(); + properties.load(propertyStream); + dataSource = (DataSource) ctx.lookup(properties.getProperty("datasource.jndi")); + logger.debug("---------------> " + dataSource.getConnection().getMetaData()); + this.taskanaEngineConfiguration = new TaskanaEngineConfiguration(dataSource, true, false); + } catch (NamingException | SQLException | IOException e) { + logger.error("Could not start Taskana: ", e); + } + } + + @ApplicationScoped + @Produces + public TaskanaEngine generateTaskEngine() throws SQLException { + return taskanaEngineConfiguration.buildTaskanaEngine(); + } + + @ApplicationScoped + @Produces + public TaskService generateTaskService() { + return taskanaEngine.getTaskService(); + } + +} diff --git a/lib/taskana-cdi/src/main/resources/META-INF/beans.xml b/lib/taskana-cdi/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..e5931ca9b --- /dev/null +++ b/lib/taskana-cdi/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + org.mybatis.cdi.JtaTransactionInterceptor + + \ No newline at end of file diff --git a/lib/taskana-cdi/src/test/java/org/taskana/RestApplication.java b/lib/taskana-cdi/src/test/java/org/taskana/RestApplication.java new file mode 100644 index 000000000..8593197d5 --- /dev/null +++ b/lib/taskana-cdi/src/test/java/org/taskana/RestApplication.java @@ -0,0 +1,8 @@ +package org.taskana; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("/rest") +public class RestApplication extends Application { +} diff --git a/lib/taskana-cdi/src/test/java/org/taskana/TaskanaEjb.java b/lib/taskana-cdi/src/test/java/org/taskana/TaskanaEjb.java new file mode 100644 index 000000000..5faa68d19 --- /dev/null +++ b/lib/taskana-cdi/src/test/java/org/taskana/TaskanaEjb.java @@ -0,0 +1,25 @@ +package org.taskana; + +import javax.ejb.Stateless; +import javax.inject.Inject; + +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.model.Task; + +@Stateless +public class TaskanaEjb { + + @Inject + private TaskService taskService; + + public TaskService getTaskService() { + return taskService; + } + + public void triggerRollback() throws NotAuthorizedException { + Task t = taskService.create(new Task()); + System.out.println("---------------->" + t.getId()); + throw new RuntimeException(); + } + +} diff --git a/lib/taskana-cdi/src/test/java/org/taskana/TaskanaProducersTest.java b/lib/taskana-cdi/src/test/java/org/taskana/TaskanaProducersTest.java new file mode 100644 index 000000000..47e1755e6 --- /dev/null +++ b/lib/taskana-cdi/src/test/java/org/taskana/TaskanaProducersTest.java @@ -0,0 +1,92 @@ +package org.taskana; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.naming.NamingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wildfly.swarm.Swarm; +import org.wildfly.swarm.arquillian.CreateSwarm; +import org.wildfly.swarm.undertow.WARArchive; + +@RunWith(Arquillian.class) +public class TaskanaProducersTest { + + @Deployment(testable = false) + public static Archive createDeployment() throws Exception { + WARArchive deployment = ShrinkWrap.create(WARArchive.class); + deployment.addPackage("org.taskana"); + deployment.addClass(TaskanaProducers.class); + deployment.addAllDependencies(); + deployment.addDependency("org.mybatis:mybatis:3.4.2"); + deployment.addDependency("org.mybatis:mybatis-cdi:1.0.0"); + deployment.addDependency("org.taskana:taskana-core:0.0.1-SNAPSHOT"); + deployment.addAsResource("META-INF/beans.xml"); + deployment.addAsResource("taskana.properties"); + deployment.addAsResource("project-defaults.yml"); + return deployment; + } + + @CreateSwarm + public static Swarm newContainer() throws Exception { + Swarm swarm = new Swarm(); + return swarm; + } + + @Before + public void init() throws SQLException, ClassNotFoundException { + } + + @Test + public void testCommit() throws SQLException, ClassNotFoundException, NamingException { + + Client client = ClientBuilder.newClient(); + client.target("http://127.0.0.1:8090/rest/test").request().get(); + + Class.forName("org.h2.Driver"); + int resultCount = 0; + try (Connection conn = DriverManager.getConnection("jdbc:h2:~/data/testdb;AUTO_SERVER=TRUE", "SA", "SA")) { + ResultSet rs = conn.createStatement().executeQuery("SELECT ID, OWNER FROM TASK"); + + while (rs.next()) { + resultCount++; + } + } + + Assert.assertEquals(1, resultCount); + + } + + + @Test + public void testRollback() throws SQLException, ClassNotFoundException, NamingException { + Client client = ClientBuilder.newClient(); + client.target("http://127.0.0.1:8090/rest/test").request().post(null); + + Class.forName("org.h2.Driver"); + int resultCount = 0; + try (Connection conn = DriverManager.getConnection("jdbc:h2:~/data/testdb;AUTO_SERVER=TRUE", "SA", "SA")) { + ResultSet rs = conn.createStatement().executeQuery("SELECT ID, OWNER FROM TASK"); + + while (rs.next()) { + resultCount++; + } + } + + Assert.assertEquals(0, resultCount); + + } + +} diff --git a/lib/taskana-cdi/src/test/java/org/taskana/TaskanaRestTest.java b/lib/taskana-cdi/src/test/java/org/taskana/TaskanaRestTest.java new file mode 100644 index 000000000..09aa52fab --- /dev/null +++ b/lib/taskana-cdi/src/test/java/org/taskana/TaskanaRestTest.java @@ -0,0 +1,45 @@ +package org.taskana; + +import javax.ejb.EJB; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.model.Task; + +@Path("/test") +public class TaskanaRestTest { + + private static final Logger logger = LoggerFactory.getLogger(TaskanaRestTest.class); + + @EJB + private TaskanaEjb taskanaEjb; + + @GET + public Response startTask() throws NotAuthorizedException { + Task result = taskanaEjb.getTaskService().create(new Task()); + logger.info(result.getId() + ":" + result.getOwner()); + return Response.status(200).entity(result.getId()).build(); + } + + @POST + public Response rollbackTask() throws NotAuthorizedException { + taskanaEjb.triggerRollback(); + return Response.status(204).build(); + } + + @DELETE + @Path("{id}") + public void completeTask(@PathParam("id") String id) throws TaskNotFoundException { + logger.info(id); + taskanaEjb.getTaskService().complete(id); + } + +} diff --git a/lib/taskana-cdi/src/test/resources/project-defaults.yml b/lib/taskana-cdi/src/test/resources/project-defaults.yml new file mode 100644 index 000000000..02ccb4913 --- /dev/null +++ b/lib/taskana-cdi/src/test/resources/project-defaults.yml @@ -0,0 +1,15 @@ +swarm: + port: + offset: 10 + datasources: + data-sources: + TestDS: + driver-name: myh2 + connection-url: jdbc:h2:~/data/testdb;AUTO_SERVER=TRUE + user-name: SA + password: SA + jdbc-drivers: + myh2: + driver-class-name: org.h2.Driver + xa-datasource-name: org.h2.jdbcx.JdbcDataSource + driver-module-name: com.h2database.h2 \ No newline at end of file diff --git a/lib/taskana-cdi/src/test/resources/taskana.properties b/lib/taskana-cdi/src/test/resources/taskana.properties new file mode 100644 index 000000000..b024ab915 --- /dev/null +++ b/lib/taskana-cdi/src/test/resources/taskana.properties @@ -0,0 +1 @@ +datasource.jndi=java:jboss/datasources/TestDS \ No newline at end of file diff --git a/lib/taskana-core/.gitignore b/lib/taskana-core/.gitignore new file mode 100644 index 000000000..9ed481cb6 --- /dev/null +++ b/lib/taskana-core/.gitignore @@ -0,0 +1,2 @@ +/.apt_generated/ +/target/ diff --git a/lib/taskana-core/pom.xml b/lib/taskana-core/pom.xml new file mode 100644 index 000000000..f679ec525 --- /dev/null +++ b/lib/taskana-core/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + org.taskana + taskana-core + 0.0.1-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + + + + + org.mybatis + mybatis + 3.4.2 + + + org.slf4j + slf4j-api + 1.7.25 + + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.8.47 + test + + + com.h2database + h2 + 1.4.194 + test + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.8.1 + test + + + org.apache.logging.log4j + log4j-core + 2.8.1 + test + + + \ No newline at end of file diff --git a/lib/taskana-core/src/main/java/org/taskana/CategoryService.java b/lib/taskana-core/src/main/java/org/taskana/CategoryService.java new file mode 100644 index 000000000..aea1d06f2 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/CategoryService.java @@ -0,0 +1,48 @@ +package org.taskana; + +import org.taskana.model.Category; + +import java.util.List; + +public interface CategoryService { + + /** + * Get all available Categories + * + * @return The List of all Categories + */ + public List selectCategories(); + + /** + * Get all Categories with given parent + * + * @param parentId + * the ID of the parent Category + * @return + */ + public List selectCategoriesByParentId(String parentId); + + /** + * Get a Category for a given id + * + * @param id + * @return the requested Category + */ + public Category selectCategoryById(String id); + + /** + * Insert a new Category + * + * @param category + * the category to insert + */ + public void insertCategory(Category category); + + /** + * Update a Category + * + * @param category + * the Category to update + */ + public void updateCategory(Category category); +} diff --git a/lib/taskana-core/src/main/java/org/taskana/TaskService.java b/lib/taskana-core/src/main/java/org/taskana/TaskService.java new file mode 100644 index 000000000..a1fa3e11c --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/TaskService.java @@ -0,0 +1,111 @@ +package org.taskana; + +import java.util.List; + +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.DueWorkbasketCounter; +import org.taskana.model.Task; +import org.taskana.model.TaskStateCounter; +import org.taskana.model.TaskState; + +public interface TaskService { + + /** + * Claim an existing task + * + * @param id + * task id + * @param userName + * user who claims the task + * @throws TaskNotFoundException + */ + public void claim(String id, String userName) throws TaskNotFoundException; + + /** + * Set task to completed + * + * @param taskId + * the task id + * @throws TaskNotFoundException + */ + public void complete(String taskId) throws TaskNotFoundException; + + /** + * Create a task by a task object + * + * @param task + * @return the created task + * @throws NotAuthorizedException + */ + public Task create(Task task) throws NotAuthorizedException; + + /** + * Get the details of a task + * + * @param taskId + * the id of the task + * @return the Task + */ + public Task getTaskById(String taskId) throws TaskNotFoundException; + + /** + * Query all tasks for a workbasket. + * + * @param workbasketId + * the workbasket to query + * @return the list of tasks, which reside in the workbasket + * @throws NotAuthorizedException + */ + public List getTasksForWorkbasket(String workbasketId) throws NotAuthorizedException; + + /** + * Query all tasks for a workbasket. + * + * @param workbasketId + * the workbasket to query + * @return the list of tasks, which reside in the workbasket + * @throws NotAuthorizedException + */ + public List getTasksForWorkbasket(List workbaskets, List states) throws NotAuthorizedException; + + /** + * This method returns all Tasks + * + * @return a {@link List} of {@link Task} + */ + public List getTasks(); + + /** + * This method counts all tasks with a given state. + * + * @param states + * the countable states + * @return a List of {@link TaskStateCounter} + */ + public List getTaskCountForState(List states); + + /** + * Count all Tasks in a given workbasket with daysInPast as Days from today + * in the past and a specific state. + * + * @param workbasketId + * @param daysInPast + * @param states + * @return + */ + public long getTaskCountForWorkbasketByDaysInPastAndState(String workbasketId, long daysInPast, List states); + + /** + * Put task into another basket + * + * @param workbasketId + * @return the updated task + * @throws NotAuthorizedException + */ + public Task transfer(String taskId, String workbasketId) + throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException; + + public List getTaskCountByWorkbasketAndDaysInPastAndState(long daysInPast, List states); +} diff --git a/lib/taskana-core/src/main/java/org/taskana/TaskanaEngine.java b/lib/taskana-core/src/main/java/org/taskana/TaskanaEngine.java new file mode 100644 index 000000000..2fb66fa36 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/TaskanaEngine.java @@ -0,0 +1,36 @@ +package org.taskana; + +import org.taskana.configuration.TaskanaEngineConfiguration; + +/** + * The TaskanaEngine represents an overall set of all needed services + */ +public interface TaskanaEngine { + + /** + * The TaskService can be used for operations on all Tasks + * + * @return the TaskService + */ + public TaskService getTaskService(); + + /** + * The WorkbasketService can be used for operations on all Workbaskets + * + * @return the TaskService + */ + public WorkbasketService getWorkbasketService(); + + /** + * The CategoryService can be used for operations on all Categories + * + * @return the TaskService + */ + public CategoryService getCategoryService(); + + /** + * The Taskana configuration + * @return the TaskanaConfiguration + */ + public TaskanaEngineConfiguration getConfiguration(); +} diff --git a/lib/taskana-core/src/main/java/org/taskana/WorkbasketService.java b/lib/taskana-core/src/main/java/org/taskana/WorkbasketService.java new file mode 100644 index 000000000..3bf257901 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/WorkbasketService.java @@ -0,0 +1,114 @@ +package org.taskana; + +import java.util.List; + +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.Workbasket; +import org.taskana.model.WorkbasketAccessItem; +import org.taskana.model.WorkbasketAuthorization; + +public interface WorkbasketService { + + /** + * Get Workbasket for a given id. + * + * @param workbasketId + * @return the requested Workbasket + */ + public Workbasket getWorkbasket(String workbasketId) throws WorkbasketNotFoundException; + + /** + * Get all available Workbaskets. + * + * @return List the list of all workbaskets + */ + public List getWorkbaskets(); + + /** + * Create a new Workbasket. + * + * @param workbasket + * The workbasket to create + * @throws NotAuthorizedException + */ + public Workbasket createWorkbasket(Workbasket workbasket); + + /** + * Update a Workbasket. + * + * + * @param workbasket + * The workbasket to update + * @throws NotAuthorizedException + */ + public Workbasket updateWorkbasket(Workbasket workbasket) throws NotAuthorizedException; + + /** + * Create a new authorization for a specific workbasket and a specific user + * + * @param workbasket + * the choosen workbasket + * @param user + * the choosen user + * @return + */ + public WorkbasketAccessItem createWorkbasketAuthorization(WorkbasketAccessItem workbasketAccessItem); + + /** + * This method updates an Workbasket Authorization + * + * @param workbasketAccessItem + * the Authorization + * @return the updated entity + */ + public WorkbasketAccessItem updateWorkbasketAuthorization(WorkbasketAccessItem workbasketAccessItem); + + /** + * Get all authorizations of the workbasket + * + * @return a WorkbasketAccessItem list + */ + public List getAllAuthorizations(); + + /** + * Deletes a specific authorization + * + * @param id + * the specific id + */ + public void deleteWorkbasketAuthorization(String id); + + /** + * This method checks the authorization with the saved one + * + * @param workbasket + * the workbasket to check + * @param userId + * the user to check + * @param authorization + * the needed Authorization + * @throws WorkbasketNotFoundException + * if the workbasket do not exist + * @throws NotAuthorizedException + */ + public void checkPermission(String workbasketId, WorkbasketAuthorization authorization) throws NotAuthorizedException; + + /** + * This method get one WorkbasketAuthorization with an id + * + * @param id + * the id + * @return the full {@link WorkbasketAccessItem} + */ + public WorkbasketAccessItem getWorkbasketAuthorization(String id); + + /** + * Get all authorizations for a Workbasket. + * + * @param workbasketId + * @return List + */ + public List getWorkbasketAuthorizations(String workbasketId); + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/configuration/DbScriptRunner.java b/lib/taskana-core/src/main/java/org/taskana/configuration/DbScriptRunner.java new file mode 100644 index 000000000..5031a1899 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/configuration/DbScriptRunner.java @@ -0,0 +1,63 @@ +package org.taskana.configuration; + +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.apache.ibatis.jdbc.ScriptRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DbScriptRunner { + + private static final Logger logger = LoggerFactory.getLogger(DbScriptRunner.class); + + private static final String SQL = "/sql"; + private static final String DB_STRUCTURE = SQL + "/db-structure.sql"; + + private DataSource dataSource; + + public DbScriptRunner(DataSource dataSource) { + super(); + this.dataSource = dataSource; + } + + /** + * Run all db scripts + * + * @throws SQLException + */ + public void run() throws SQLException { + StringWriter outWriter = new StringWriter(); + PrintWriter logWriter = new PrintWriter(outWriter); + + StringWriter errorWriter = new StringWriter(); + PrintWriter errorLogWriter = new PrintWriter(errorWriter); + + ScriptRunner runner = new ScriptRunner(dataSource.getConnection()); + logger.debug(dataSource.getConnection().getMetaData().toString()); + + runner.setStopOnError(true); + runner.setLogWriter(logWriter); + runner.setErrorLogWriter(errorLogWriter); + + runner.runScript(new InputStreamReader(this.getClass().getResourceAsStream(DB_STRUCTURE))); + runner.closeConnection(); + + logger.debug(outWriter.toString()); + if (!errorWriter.toString().trim().isEmpty()) { + logger.error(errorWriter.toString()); + } + } + + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/configuration/TaskanaEngineConfiguration.java b/lib/taskana-core/src/main/java/org/taskana/configuration/TaskanaEngineConfiguration.java new file mode 100644 index 000000000..b6cd9c9a9 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/configuration/TaskanaEngineConfiguration.java @@ -0,0 +1,96 @@ +package org.taskana.configuration; + +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.apache.ibatis.datasource.pooled.PooledDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.taskana.TaskanaEngine; +import org.taskana.impl.TaskanaEngineImpl; + +/** + * This central class creates the TaskanaEngine and needs all the information + * about DB and Security + */ +public class TaskanaEngineConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(TaskanaEngineConfiguration.class); + + private static final String USER_PASSWORD = "sa"; + private static final String JDBC_H2_MEM_TASKANA = "jdbc:h2:mem:taskana"; + private static final String H2_DRIVER = "org.h2.Driver"; + + protected DataSource dataSource; + protected DbScriptRunner dbScriptRunner; + + // global switch to enable JAAS based authentication and Taskana + // authorizations + protected boolean securityEnabled; + protected boolean useContainerManagedTransactions; + + public TaskanaEngineConfiguration() { + } + + public TaskanaEngineConfiguration(DataSource dataSource, boolean useContainerManagedTransactions) + throws SQLException { + this(dataSource, useContainerManagedTransactions, true); + } + + public TaskanaEngineConfiguration(DataSource dataSource, boolean useContainerManagedTransactions, + boolean securityEnabled) throws SQLException { + this.useContainerManagedTransactions = useContainerManagedTransactions; + + if (dataSource != null) { + this.dataSource = dataSource; + } else { + // use default In Memory datasource + this.dataSource = createDefaultDataSource(); + } + dbScriptRunner = new DbScriptRunner(this.dataSource); + dbScriptRunner.run(); + + this.securityEnabled = securityEnabled; + } + + public DataSource createDefaultDataSource() { + logger.warn("No datasource is provided. A inmemory db is used: " + + "'org.h2.Driver', 'jdbc:h2:mem:taskana', 'sa', 'sa'"); + return createDatasource(H2_DRIVER, JDBC_H2_MEM_TASKANA, USER_PASSWORD, USER_PASSWORD); + } + + /** + * This method creates the TaskanaEngine without an sqlSessionFactory + * + * @return the TaskanaEngine + * @throws SQLException + */ + public TaskanaEngine buildTaskanaEngine() throws SQLException { + return new TaskanaEngineImpl(this); + } + + /** + * This method creates a PooledDataSource, if the needed properties are + * provided + * + * @param dbConfiguration + * @return DataSource + */ + public DataSource createDatasource(String driver, String jdbcUrl, String username, String password) { + return new PooledDataSource(driver, jdbcUrl, username, password); + } + + public boolean isSecurityEnabled() { + return this.securityEnabled; + } + + public DataSource getDatasource() { + return this.dataSource; + } + + public boolean getUseContainerManagedTransactions() { + return this.useContainerManagedTransactions; + } + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/exceptions/NotAuthorizedException.java b/lib/taskana-core/src/main/java/org/taskana/exceptions/NotAuthorizedException.java new file mode 100644 index 000000000..f4ae9fdf8 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/exceptions/NotAuthorizedException.java @@ -0,0 +1,10 @@ +package org.taskana.exceptions; + +@SuppressWarnings("serial") +public class NotAuthorizedException extends Exception { + + public NotAuthorizedException(String msg) { + super(msg); + } + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/exceptions/NotFoundException.java b/lib/taskana-core/src/main/java/org/taskana/exceptions/NotFoundException.java new file mode 100644 index 000000000..eab40a39b --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/exceptions/NotFoundException.java @@ -0,0 +1,10 @@ +package org.taskana.exceptions; + +@SuppressWarnings("serial") +public class NotFoundException extends Exception { + + public NotFoundException(String id) { + super(id); + } + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/exceptions/TaskNotFoundException.java b/lib/taskana-core/src/main/java/org/taskana/exceptions/TaskNotFoundException.java new file mode 100644 index 000000000..8067697dd --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/exceptions/TaskNotFoundException.java @@ -0,0 +1,9 @@ +package org.taskana.exceptions; + +@SuppressWarnings("serial") +public class TaskNotFoundException extends NotFoundException { + + public TaskNotFoundException(String id) { + super("Task '" + id + "' not found"); + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/exceptions/WorkbasketNotFoundException.java b/lib/taskana-core/src/main/java/org/taskana/exceptions/WorkbasketNotFoundException.java new file mode 100644 index 000000000..ff2843647 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/exceptions/WorkbasketNotFoundException.java @@ -0,0 +1,9 @@ +package org.taskana.exceptions; + +@SuppressWarnings("serial") +public class WorkbasketNotFoundException extends NotFoundException { + + public WorkbasketNotFoundException(String id) { + super("Workbasket with '" + id + "' not found"); + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/impl/CategoryServiceImpl.java b/lib/taskana-core/src/main/java/org/taskana/impl/CategoryServiceImpl.java new file mode 100644 index 000000000..67fe2e91e --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/impl/CategoryServiceImpl.java @@ -0,0 +1,73 @@ +package org.taskana.impl; + +import org.taskana.CategoryService; +import org.taskana.model.Category; +import org.taskana.model.mappings.CategoryMapper; + +import java.sql.Date; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +public class CategoryServiceImpl implements CategoryService { + + private CategoryMapper categoryMapper; + + public CategoryServiceImpl(CategoryMapper categoryMapper) { + super(); + this.categoryMapper = categoryMapper; + } + + @Override + public List selectCategories() { + final List rootCategories = categoryMapper.findByParentId(""); + populateChildcategories(rootCategories); + return rootCategories; + } + + private void populateChildcategories(final List categories) { + for (Category category : categories) { + List childCategories = categoryMapper.findByParentId(category.getId()); + category.setChildren(childCategories); + populateChildcategories(childCategories); + } + } + + @Override + public List selectCategoriesByParentId(String parentId) { + return categoryMapper.findByParentId(parentId); + } + + @Override + public void insertCategory(Category category) { + category.setId(UUID.randomUUID().toString()); + category.setCreated(Date.valueOf(LocalDate.now())); + category.setModified(Date.valueOf(LocalDate.now())); + this.checkServiceLevel(category); + + categoryMapper.insert(category); + } + + @Override + public void updateCategory(Category category) { + category.setModified(Date.valueOf(LocalDate.now())); + this.checkServiceLevel(category); + + categoryMapper.update(category); + } + + @Override + public Category selectCategoryById(String id) { + return categoryMapper.findById(id); + } + + private void checkServiceLevel(Category category){ + if(category.getServiceLevel()!= null){ + try { + java.time.Duration.parse(category.getServiceLevel()); + } catch (Exception e){ + throw new IllegalArgumentException("Invalid timestamp. Please use the format 'PddDThhHmmM'"); + } + } + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/impl/TaskServiceImpl.java b/lib/taskana-core/src/main/java/org/taskana/impl/TaskServiceImpl.java new file mode 100644 index 000000000..63d44aeaf --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/impl/TaskServiceImpl.java @@ -0,0 +1,162 @@ +package org.taskana.impl; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.taskana.TaskService; +import org.taskana.TaskanaEngine; +import org.taskana.WorkbasketService; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.DueWorkbasketCounter; +import org.taskana.model.Task; +import org.taskana.model.TaskState; +import org.taskana.model.TaskStateCounter; +import org.taskana.model.Workbasket; +import org.taskana.model.WorkbasketAuthorization; +import org.taskana.model.mappings.TaskMapper; + +public class TaskServiceImpl implements TaskService { + + private static final Logger logger = LoggerFactory.getLogger(TaskServiceImpl.class); + + private TaskanaEngine taskanaEngine; + private TaskMapper taskMapper; + + public TaskServiceImpl(TaskanaEngine taskanaEngine, TaskMapper taskMapper) { + super(); + this.taskanaEngine = taskanaEngine; + this.taskMapper = taskMapper; + } + + @Override + public void claim(String id, String userName) throws TaskNotFoundException { + Task task = taskMapper.findById(id); + if (task != null) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + task.setOwner(userName); + task.setModified(now); + task.setClaimed(now); + task.setState(TaskState.CLAIMED); + taskMapper.update(task); + logger.debug("User '{}' claimed task '{}'.",userName, id); + } else { + throw new TaskNotFoundException(id); + } + } + + @Override + public void complete(String id) throws TaskNotFoundException { + Task task = taskMapper.findById(id); + if (task != null) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + task.setCompleted(now); + task.setModified(now); + task.setState(TaskState.COMPLETED); + taskMapper.update(task); + logger.debug("Task '{}' completed.",id); + } else { + throw new TaskNotFoundException(id); + } + } + + @Override + public Task create(Task task) throws NotAuthorizedException { + taskanaEngine.getWorkbasketService().checkPermission(task.getWorkbasketId(), WorkbasketAuthorization.APPEND); + + Timestamp now = new Timestamp(System.currentTimeMillis()); + task.setId(UUID.randomUUID().toString()); + task.setState(TaskState.READY); + task.setCreated(now); + task.setModified(now); + taskMapper.insert(task); + logger.debug("Task '{}' created.", task.getId()); + return task; + } + + @Override + public Task getTaskById(String id) throws TaskNotFoundException { + Task task = taskMapper.findById(id); + if (task != null) { + return task; + } else { + throw new TaskNotFoundException(id); + } + } + + @Override + public List getTasksForWorkbasket(String workbasketId) throws NotAuthorizedException { + taskanaEngine.getWorkbasketService().checkPermission(workbasketId, WorkbasketAuthorization.OPEN); + + return taskMapper.findByWorkBasketId(workbasketId); + } + + @Override + public List getTasksForWorkbasket(List workbasketIds, List states) + throws NotAuthorizedException { + + for (String workbasket : workbasketIds) { + taskanaEngine.getWorkbasketService().checkPermission(workbasket, WorkbasketAuthorization.OPEN); + } + + return taskMapper.findByWorkbasketIdsAndStates(workbasketIds, states); + } + + @Override + public List getTasks() { + return taskMapper.findAll(); + } + + @Override + public List getTaskCountForState(List states) { + return taskMapper.getTaskCountForState(states); + } + + @Override + public long getTaskCountForWorkbasketByDaysInPastAndState(String workbasketId, long daysInPast, + List states) { + LocalDate time = LocalDate.now(); + time = time.minusDays(daysInPast); + Date fromDate = Date.valueOf(time); + return taskMapper.getTaskCountForWorkbasketByDaysInPastAndState(workbasketId, fromDate, states); + } + + @Override + public Task transfer(String taskId, String destinationWorkbasketId) + throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException { + Task task = getTaskById(taskId); + + // transfer requires TRANSFER in source and APPEND on destination workbasket + taskanaEngine.getWorkbasketService().checkPermission(destinationWorkbasketId, WorkbasketAuthorization.APPEND); + taskanaEngine.getWorkbasketService().checkPermission(task.getWorkbasketId(), WorkbasketAuthorization.TRANSFER); + + // if security is disabled, the implicit existance check on the destination workbasket has been skipped and needs to be performed + if (!taskanaEngine.getConfiguration().isSecurityEnabled()) { + taskanaEngine.getWorkbasketService().getWorkbasket(destinationWorkbasketId); + } + + // transfer task from source to destination workbasket + task.setWorkbasketId(destinationWorkbasketId); + task.setModified(Timestamp.valueOf(LocalDateTime.now())); + taskMapper.update(task); + + return getTaskById(taskId); + } + + @Override + public List getTaskCountByWorkbasketAndDaysInPastAndState(long daysInPast, + List states) { + LocalDate time = LocalDate.now(); + time = time.minusDays(daysInPast); + Date fromDate = Date.valueOf(time); + return taskMapper.getTaskCountByWorkbasketIdAndDaysInPastAndState(fromDate, states); + } + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/impl/TaskanaEngineImpl.java b/lib/taskana-core/src/main/java/org/taskana/impl/TaskanaEngineImpl.java new file mode 100644 index 000000000..451d596f6 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/impl/TaskanaEngineImpl.java @@ -0,0 +1,110 @@ +package org.taskana.impl; + +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; +import org.taskana.CategoryService; +import org.taskana.TaskService; +import org.taskana.TaskanaEngine; +import org.taskana.WorkbasketService; +import org.taskana.configuration.TaskanaEngineConfiguration; +import org.taskana.model.mappings.CategoryMapper; +import org.taskana.model.mappings.DistributionTargetMapper; +import org.taskana.model.mappings.TaskMapper; +import org.taskana.model.mappings.WorkbasketAccessMapper; +import org.taskana.model.mappings.WorkbasketMapper; + +public class TaskanaEngineImpl implements TaskanaEngine { + + private static final String DEFAULT = "default"; + + protected TaskanaEngineConfiguration taskanaEngineConfiguration; + protected SqlSession session; + protected TransactionFactory transactionFactory; + + private TaskMapper taskMapper; + private WorkbasketMapper workbasketMapper; + private DistributionTargetMapper distributionTargetMapper; + private CategoryMapper categoryMapper; + private WorkbasketAccessMapper workbasketAccessMapper; + + private TaskServiceImpl taskServiceImpl; + private WorkbasketServiceImpl workbasketServiceImpl; + + public TaskanaEngineImpl(TaskanaEngineConfiguration taskanaEngineConfiguration) { + this.taskanaEngineConfiguration = taskanaEngineConfiguration; + + createTransactionFactory(taskanaEngineConfiguration.getUseContainerManagedTransactions()); + + this.session = createSqlSessionFactory().openSession(); + this.taskMapper = session.getMapper(TaskMapper.class); + this.workbasketMapper = session.getMapper(WorkbasketMapper.class); + this.distributionTargetMapper = session.getMapper(DistributionTargetMapper.class); + this.categoryMapper = session.getMapper(CategoryMapper.class); + this.workbasketAccessMapper = session.getMapper(WorkbasketAccessMapper.class); + } + + @Override + public TaskService getTaskService() { + this.taskServiceImpl = new TaskServiceImpl(this, this.taskMapper); + return taskServiceImpl; + } + + @Override + public WorkbasketService getWorkbasketService() { + this.workbasketServiceImpl = new WorkbasketServiceImpl(this, this.workbasketMapper, + this.distributionTargetMapper, this.workbasketAccessMapper); + return workbasketServiceImpl; + } + + @Override + public CategoryService getCategoryService() { + return new CategoryServiceImpl(this.categoryMapper); + } + + @Override + public TaskanaEngineConfiguration getConfiguration() { + return this.taskanaEngineConfiguration; + } + + /** + * Close session manually, to be done, if a JdbcTransactionFactory is used. + * Perhaps it is better to separate the commit and the closing mechanism ... + */ + public void closeSession() { + this.session.commit(); + this.session.close(); + } + + /** + * This method creates the sqlSessionFactory of myBatis. It integrates all + * the SQL mappers + * + * @return a {@link SqlSessionFactory} + */ + private SqlSessionFactory createSqlSessionFactory() { + Environment environment = new Environment(DEFAULT, this.transactionFactory, taskanaEngineConfiguration.getDatasource()); + Configuration configuration = new Configuration(environment); + // add mappers + configuration.addMapper(TaskMapper.class); + configuration.addMapper(WorkbasketMapper.class); + configuration.addMapper(DistributionTargetMapper.class); + configuration.addMapper(CategoryMapper.class); + configuration.addMapper(WorkbasketAccessMapper.class); + return new SqlSessionFactoryBuilder().build(configuration); + } + + private void createTransactionFactory(boolean useContainerManagedTransactions) { + if (useContainerManagedTransactions) { + this.transactionFactory = new ManagedTransactionFactory(); + } else { + this.transactionFactory = new JdbcTransactionFactory(); + } + } + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/impl/WorkbasketServiceImpl.java b/lib/taskana-core/src/main/java/org/taskana/impl/WorkbasketServiceImpl.java new file mode 100644 index 000000000..3ac64ee7b --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/impl/WorkbasketServiceImpl.java @@ -0,0 +1,159 @@ +package org.taskana.impl; + +import java.sql.Timestamp; +import java.util.List; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.taskana.TaskanaEngine; +import org.taskana.WorkbasketService; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.Workbasket; +import org.taskana.model.WorkbasketAccessItem; +import org.taskana.model.WorkbasketAuthorization; +import org.taskana.model.mappings.DistributionTargetMapper; +import org.taskana.model.mappings.WorkbasketAccessMapper; +import org.taskana.model.mappings.WorkbasketMapper; +import org.taskana.security.CurrentUserContext; + +public class WorkbasketServiceImpl implements WorkbasketService { + + private static final Logger logger = LoggerFactory.getLogger(WorkbasketServiceImpl.class); + + private TaskanaEngine taskanaEngine; + + private WorkbasketMapper workbasketMapper; + private DistributionTargetMapper distributionTargetMapper; + private WorkbasketAccessMapper workbasketAccessMapper; + + public WorkbasketServiceImpl() { + } + + public WorkbasketServiceImpl(TaskanaEngine taskanaEngine, WorkbasketMapper workbasketMapper, + DistributionTargetMapper distributionTargetMapper, WorkbasketAccessMapper workbasketAccessMapper) { + this.taskanaEngine = taskanaEngine; + this.workbasketMapper = workbasketMapper; + this.distributionTargetMapper = distributionTargetMapper; + this.workbasketAccessMapper = workbasketAccessMapper; + } + + @Override + public Workbasket getWorkbasket(String workbasketId) throws WorkbasketNotFoundException { + Workbasket workbasket = workbasketMapper.findById(workbasketId); + if (workbasket == null) { + throw new WorkbasketNotFoundException(workbasketId); + } + return workbasket; + } + + @Override + public List getWorkbaskets() { + List workbaskets = workbasketMapper.findAll(); + return workbaskets; + } + + @Override + public Workbasket createWorkbasket(Workbasket workbasket) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + workbasket.setCreated(now); + workbasket.setModified(now); + if (workbasket.getId() == null || workbasket.getId().isEmpty()) { + workbasket.setId(UUID.randomUUID().toString()); + } + workbasketMapper.insert(workbasket); + logger.debug("Workbasket '{}' created", workbasket.getId()); + if (workbasket.getDistributionTargets() != null) { + for (Workbasket distributionTarget : workbasket.getDistributionTargets()) { + if (workbasketMapper.findById(distributionTarget.getId()) == null) { + distributionTarget.setCreated(now); + distributionTarget.setModified(now); + workbasketMapper.insert(distributionTarget); + logger.debug("Workbasket '{}' created", distributionTarget.getId()); + } + distributionTargetMapper.insert(workbasket.getId(), distributionTarget.getId()); + } + } + return workbasketMapper.findById(workbasket.getId()); + } + + @Override + public Workbasket updateWorkbasket(Workbasket workbasket) throws NotAuthorizedException { + workbasket.setModified(new Timestamp(System.currentTimeMillis())); + workbasketMapper.update(workbasket); + List oldDistributionTargets = distributionTargetMapper.findBySourceId(workbasket.getId()); + List distributionTargets = workbasket.getDistributionTargets(); + for (Workbasket distributionTarget : distributionTargets) { + if (!oldDistributionTargets.contains(distributionTarget.getId())) { + if (workbasketMapper.findById(distributionTarget.getId()) == null) { + workbasketMapper.insert(distributionTarget); + logger.debug("Workbasket '{}' created", distributionTarget.getId()); + } + distributionTargetMapper.insert(workbasket.getId(), distributionTarget.getId()); + } else { + oldDistributionTargets.remove(distributionTarget.getId()); + } + } + distributionTargetMapper.deleteMultiple(workbasket.getId(), oldDistributionTargets); + logger.debug("Workbasket '{}' updated", workbasket.getId()); + return workbasketMapper.findById(workbasket.getId()); + } + + @Override + public WorkbasketAccessItem createWorkbasketAuthorization(WorkbasketAccessItem workbasketAccessItem) { + workbasketAccessItem.setId(UUID.randomUUID().toString()); + workbasketAccessMapper.insert(workbasketAccessItem); + return workbasketAccessItem; + } + + @Override + public WorkbasketAccessItem getWorkbasketAuthorization(String id) { + return workbasketAccessMapper.findById(id); + } + + @Override + public void deleteWorkbasketAuthorization(String id) { + workbasketAccessMapper.delete(id); + } + + @Override + public List getAllAuthorizations() { + return workbasketAccessMapper.findAll(); + } + + @Override + public void checkPermission(String workbasketId, WorkbasketAuthorization workbasketAuthorization) + throws NotAuthorizedException { + + // Skip permission check is security is not enabled + if (!taskanaEngine.getConfiguration().isSecurityEnabled()) { + logger.debug("Skipping permissions check since security is disabled."); + return; + } + + String userId = CurrentUserContext.getUserid(); + logger.debug("Verifying that {} has the permission {} on workbasket {}", userId, workbasketAuthorization.name(), + workbasketId); + + List accessItems = workbasketAccessMapper + .findByWorkbasketAndUserAndAuthorization(workbasketId, userId, workbasketAuthorization.name()); + + if (accessItems.size() <= 0) { + throw new NotAuthorizedException("Not authorized. Authorization '" + workbasketAuthorization.name() + + "' on workbasket '" + workbasketId + "' is needed."); + + } + } + + @Override + public WorkbasketAccessItem updateWorkbasketAuthorization(WorkbasketAccessItem workbasketAccessItem) { + workbasketAccessMapper.update(workbasketAccessItem); + return workbasketAccessItem; + } + + @Override + public List getWorkbasketAuthorizations(String workbasketId) { + return workbasketAccessMapper.findByWorkbasketId(workbasketId); + } +} \ No newline at end of file diff --git a/lib/taskana-core/src/main/java/org/taskana/model/Category.java b/lib/taskana-core/src/main/java/org/taskana/model/Category.java new file mode 100644 index 000000000..afa1bc50c --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/Category.java @@ -0,0 +1,106 @@ +package org.taskana.model; + +import java.sql.Date; +import java.util.ArrayList; +import java.util.List; + +/** + * Category entity + */ +public class Category { + + private String id; + private String tenantId; + private String parentCategoryId; + private Date created; + private Date modified; + private String name; + private String description; + private int priority; + private String serviceLevel; //PddDThhHmmM + private List children = new ArrayList<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getParentCategoryId() { + return parentCategoryId; + } + + public void setParentCategoryId(String parentCategoryId) { + this.parentCategoryId = parentCategoryId; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public String getServiceLevel() { + return serviceLevel; + } + + public void setServiceLevel(String serviceLevel) { + this.serviceLevel = serviceLevel; + } + + public List getChildren() { + return children; + } + + public void addChild(Category child) { + children.add(child); + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/DueWorkbasketCounter.java b/lib/taskana-core/src/main/java/org/taskana/model/DueWorkbasketCounter.java new file mode 100644 index 000000000..308c299da --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/DueWorkbasketCounter.java @@ -0,0 +1,37 @@ +package org.taskana.model; + +import java.sql.Date; + +/** + * DueWorkbasketCounter entity + */ +public class DueWorkbasketCounter { + + private Date due; + private String workbasketId; + private long taskCounter; + + public Date getDue() { + return due; + } + + public void setDue(Date due) { + this.due = due; + } + + public String getWorkbasketId() { + return workbasketId; + } + + public void setWorkbasketId(String workbasketId) { + this.workbasketId = workbasketId; + } + + public long getTaskCounter() { + return taskCounter; + } + + public void setTaskCounter(long taskCounter) { + this.taskCounter = taskCounter; + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/Task.java b/lib/taskana-core/src/main/java/org/taskana/model/Task.java new file mode 100644 index 000000000..dbd214504 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/Task.java @@ -0,0 +1,168 @@ +package org.taskana.model; + +import java.sql.Timestamp; + +/** + * Task entity + */ +public class Task { + + private String id; + private String tenantId; + private Timestamp created; + private Timestamp claimed; + private Timestamp completed; + private Timestamp modified; + private Timestamp planned; + private Timestamp due; + private String name; + private String description; + private int priority; + private TaskState state; + private String type; + private String workbasketId; + private String owner; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public Timestamp getCreated() { + return created; + } + + public void setCreated(Timestamp created) { + this.created = created; + } + + public Timestamp getClaimed() { + return claimed; + } + + public void setClaimed(Timestamp claimed) { + this.claimed = claimed; + } + + public Timestamp getCompleted() { + return completed; + } + + public void setCompleted(Timestamp completed) { + this.completed = completed; + } + + public Timestamp getModified() { + return modified; + } + + public void setModified(Timestamp modified) { + this.modified = modified; + } + + public Timestamp getPlanned() { + return planned; + } + + public void setPlanned(Timestamp planned) { + this.planned = planned; + } + + public Timestamp getDue() { + return due; + } + + public void setDue(Timestamp due) { + this.due = due; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public TaskState getState() { + return state; + } + + public void setState(TaskState state) { + this.state = state; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWorkbasketId() { + return workbasketId; + } + + public void setWorkbasketId(String workbasketId) { + this.workbasketId = workbasketId; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("TASK("); + sb.append("id="+id); + sb.append(", tenantId="+tenantId); + sb.append(", created="+created); + sb.append(", claimed="+claimed); + sb.append(", completed="+completed); + sb.append(", modified="+modified); + sb.append(", planned="+planned); + sb.append(", due="+due); + sb.append(", name="+name); + sb.append(", description="+description); + sb.append(", priority="+priority); + sb.append(", state="+state); + sb.append(", type="+type); + sb.append(", workbasketId="+workbasketId); + sb.append(", owner="+owner); + sb.append(")"); + return sb.toString(); + + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/TaskState.java b/lib/taskana-core/src/main/java/org/taskana/model/TaskState.java new file mode 100644 index 000000000..01783960a --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/TaskState.java @@ -0,0 +1,8 @@ +package org.taskana.model; + +/** + * This enum contains all status of the tasks + */ +public enum TaskState { + READY, CLAIMED, COMPLETED +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/TaskStateCounter.java b/lib/taskana-core/src/main/java/org/taskana/model/TaskStateCounter.java new file mode 100644 index 000000000..e6a4a1d73 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/TaskStateCounter.java @@ -0,0 +1,26 @@ +package org.taskana.model; + +/** + * TaskStateCounter entity + */ +public class TaskStateCounter { + + private TaskState state; + private long counter; + + public long getCounter() { + return counter; + } + + public void setCounter(long counter) { + this.counter = counter; + } + + public TaskState getState() { + return state; + } + + public void setState(TaskState state) { + this.state = state; + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/Workbasket.java b/lib/taskana-core/src/main/java/org/taskana/model/Workbasket.java new file mode 100644 index 000000000..185a16917 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/Workbasket.java @@ -0,0 +1,84 @@ +package org.taskana.model; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +/** + * Workbasket entity + */ +public class Workbasket { + + private String id; + private String tenantId; + private Timestamp created; + private Timestamp modified; + private String name; + private String description; + private String owner; + private List distributionTargets = new ArrayList<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public Timestamp getCreated() { + return created; + } + + public void setCreated(Timestamp created) { + this.created = created; + } + + public Timestamp getModified() { + return modified; + } + + public void setModified(Timestamp modified) { + this.modified = modified; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public List getDistributionTargets() { + return distributionTargets; + } + + public void setDistributionTargets(List distributionTargets) { + this.distributionTargets = distributionTargets; + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/WorkbasketAccessItem.java b/lib/taskana-core/src/main/java/org/taskana/model/WorkbasketAccessItem.java new file mode 100644 index 000000000..7207b775d --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/WorkbasketAccessItem.java @@ -0,0 +1,89 @@ +package org.taskana.model; + +/** + * WorkbasketAccessItem entity + */ +public class WorkbasketAccessItem { + + private String id; + private String workbasketId; + private String userId; + private String groupId; + private boolean read; + private boolean open; + private boolean append; + private boolean transfer; + private boolean distribute; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getWorkbasketId() { + return workbasketId; + } + + public void setWorkbasketId(String workbasketId) { + this.workbasketId = workbasketId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public boolean isRead() { + return read; + } + + public void setRead(boolean read) { + this.read = read; + } + + public boolean isOpen() { + return open; + } + + public void setOpen(boolean open) { + this.open = open; + } + + public boolean isAppend() { + return append; + } + + public void setAppend(boolean append) { + this.append = append; + } + + public boolean isTransfer() { + return transfer; + } + + public void setTransfer(boolean transfer) { + this.transfer = transfer; + } + + public boolean isDistribute() { + return distribute; + } + + public void setDistribute(boolean distribute) { + this.distribute = distribute; + } +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/WorkbasketAuthorization.java b/lib/taskana-core/src/main/java/org/taskana/model/WorkbasketAuthorization.java new file mode 100644 index 000000000..4cbd5cf95 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/WorkbasketAuthorization.java @@ -0,0 +1,8 @@ +package org.taskana.model; + +/** + * This enum contains all permission values for the workbaskets. + */ +public enum WorkbasketAuthorization { + READ, OPEN, APPEND, TRANSFER, DISTRIBUTE +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/mappings/CategoryMapper.java b/lib/taskana-core/src/main/java/org/taskana/model/mappings/CategoryMapper.java new file mode 100644 index 000000000..c41d669ba --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/mappings/CategoryMapper.java @@ -0,0 +1,35 @@ +package org.taskana.model.mappings; + +import org.apache.ibatis.annotations.*; +import org.taskana.model.Category; + +import java.util.List; + +public interface CategoryMapper { + + @Select("SELECT * FROM BUSINESS_CATEGORY ORDER BY ID") + @Results({ + @Result(property="id", column="ID"), + @Result(property="tenantId", column="TENANT_ID"), + @Result(property="parentCategoryId", column="PARENT_CATEGORY_ID"), + @Result(property="created", column="CREATED"), + @Result(property="modified", column="MODIFIED"), + @Result(property="name", column="NAME"), + @Result(property="description", column="DESCRIPTION"), + @Result(property="priority", column="PRIORITY"), + @Result(property="serviceLevel", column="SERVICE_LEVEL") + }) + List findAll(); + + @Select("SELECT * FROM BUSINESS_CATEGORY WHERE PARENT_CATEGORY_ID = #{parentCategoryId} ORDER BY ID") + List findByParentId(@Param("parentCategoryId") String parentId); + + @Select("SELECT * FROM BUSINESS_CATEGORY WHERE ID = #{categoryId}") + Category findById(@Param("categoryId") String categoryId); + + @Insert("INSERT INTO BUSINESS_CATEGORY (ID, TENANT_ID, PARENT_CATEGORY_ID, CREATED, NAME, DESCRIPTION, PRIORITY, SERVICE_LEVEL) VALUES (#{category.id}, #{category.tenantId}, #{category.parentCategoryId}, #{category.created}, #{category.name}, #{category.description}, #{category.priority}, #{category.serviceLevel})") + void insert(@Param("category") Category category); + + @Update(value = "UPDATE BUSINESS_CATEGORY SET TENANT_ID = #{category.tenantId}, PARENT_CATEGORY_ID = #{category.parentCategoryId}, NAME = #{category.name}, DESCRIPTION = #{category.description}, PRIORITY = #{category.priority}, SERVICE_LEVEL = #{category.serviceLevel}, MODIFIED = #{category.modified} WHERE ID = #{category.id}") + void update(@Param("category") Category category); +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/mappings/DistributionTargetMapper.java b/lib/taskana-core/src/main/java/org/taskana/model/mappings/DistributionTargetMapper.java new file mode 100644 index 000000000..560032326 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/mappings/DistributionTargetMapper.java @@ -0,0 +1,23 @@ +package org.taskana.model.mappings; + +import java.util.List; + +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +public interface DistributionTargetMapper { + + @Insert("INSERT INTO DISTRIBUTION_TARGETS (SOURCE_ID, TARGET_ID) VALUES (#{sourceId}, #{targetId})") + void insert(@Param("sourceId") String sourceId, @Param("targetId") String targetId); + + @Delete("DELETE FROM DISTRIBUTION_TARGETS WHERE SOURCE_ID = #{sourceId} AND TARGET_ID = #{targetId}") + void delete(@Param("sourceId") String sourceId, @Param("targetId") String targetId); + + @Select("SELECT TARGET_ID FROM DISTRIBUTION_TARGETS WHERE SOURCE_ID = #{sourceId}") + List findBySourceId(@Param("sourceId") String sourceId); + + @Delete("") + void deleteMultiple(@Param("sourceId") String sourceId, @Param("targetId") List targetId); +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/mappings/TaskMapper.java b/lib/taskana-core/src/main/java/org/taskana/model/mappings/TaskMapper.java new file mode 100644 index 000000000..ac4e09781 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/mappings/TaskMapper.java @@ -0,0 +1,137 @@ +package org.taskana.model.mappings; + +import java.sql.Date; +import java.util.List; + +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; +import org.taskana.model.DueWorkbasketCounter; +import org.taskana.model.Task; +import org.taskana.model.TaskStateCounter; +import org.taskana.model.TaskState; + +public interface TaskMapper { + + @Select("SELECT ID, TENANT_ID, CREATED, CLAIMED, COMPLETED, MODIFIED, PLANNED, DUE, NAME, DESCRIPTION, PRIORITY, STATE, TYPE, WORKBASKETID, OWNER " + + "FROM task " + + "WHERE ID = #{id}") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "tenantId", column = "TENANT_ID"), + @Result(property = "created", column = "CREATED"), + @Result(property = "claimed", column = "CLAIMED"), + @Result(property = "completed", column = "COMPLETED"), + @Result(property = "modified", column = "MODIFIED"), + @Result(property = "planned", column = "PLANNED"), + @Result(property = "due", column = "DUE"), + @Result(property = "name", column = "NAME"), + @Result(property = "description", column = "DESCRIPTION"), + @Result(property = "priority", column = "PRIORITY"), + @Result(property = "state", column = "STATE"), + @Result(property = "type", column = "TYPE"), + @Result(property = "workbasketId", column = "WORKBASKETID"), + @Result(property = "owner", column = "OWNER")}) + Task findById(String id); + + @Select("SELECT ID, TENANT_ID, CREATED, CLAIMED, COMPLETED, MODIFIED, PLANNED, DUE, NAME, DESCRIPTION, PRIORITY, STATE, TYPE, WORKBASKETID, OWNER " + + "FROM task " + + "WHERE WORKBASKETID = #{workbasketId} " + + "ORDER BY ID") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "tenantId", column = "TENANT_ID"), + @Result(property = "created", column = "CREATED"), + @Result(property = "claimed", column = "CLAIMED"), + @Result(property = "completed", column = "COMPLETED"), + @Result(property = "modified", column = "MODIFIED"), + @Result(property = "planned", column = "PLANNED"), + @Result(property = "due", column = "DUE"), + @Result(property = "name", column = "NAME"), + @Result(property = "description", column = "DESCRIPTION"), + @Result(property = "priority", column = "PRIORITY"), + @Result(property = "state", column = "STATE"), + @Result(property = "type", column = "TYPE"), + @Result(property = "workbasketId", column = "WORKBASKETID"), + @Result(property = "owner", column = "OWNER")}) + List findByWorkBasketId(String workbasketId); + + @Select("") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "tenantId", column = "TENANT_ID"), + @Result(property = "created", column = "CREATED"), + @Result(property = "claimed", column = "CLAIMED"), + @Result(property = "completed", column = "COMPLETED"), + @Result(property = "modified", column = "MODIFIED"), + @Result(property = "planned", column = "PLANNED"), + @Result(property = "due", column = "DUE"), + @Result(property = "name", column = "NAME"), + @Result(property = "description", column = "DESCRIPTION"), + @Result(property = "priority", column = "PRIORITY"), + @Result(property = "state", column = "STATE"), + @Result(property = "type", column = "TYPE"), + @Result(property = "workbasketId", column = "WORKBASKETID"), + @Result(property = "owner", column = "OWNER")}) + List findByWorkbasketIdsAndStates(@Param("workbasketIds") List workbasketIds, @Param("states") List states); + + @Select("SELECT * FROM TASK") + List findAll(); + + @Select("") + @Results({ + @Result(column="STATE", property="state"), + @Result(column="counter", property="counter") + }) + List getTaskCountForState(@Param("status") List status); + + @Select("") + long getTaskCountForWorkbasketByDaysInPastAndState(@Param("workbasketId")String workbasketId, @Param("fromDate") Date fromDate, @Param("status") List states); + + @Select("") + @Results({ + @Result(column="DUE_DATE", property="due"), + @Result(column="WORKBASKETID", property="workbasketId"), + @Result(column="counter", property="taskCounter") + }) + List getTaskCountByWorkbasketIdAndDaysInPastAndState(@Param("fromDate") Date fromDate, @Param("status") List states); + + @Insert("INSERT INTO TASK(ID, TENANT_ID, CREATED, CLAIMED, COMPLETED, MODIFIED, PLANNED, DUE, NAME, DESCRIPTION, PRIORITY, STATE, TYPE, WORKBASKETID, OWNER) VALUES(#{id}, #{tenantId}, #{created}, #{claimed}, #{completed}, #{modified}, #{planned}, #{due}, #{name}, #{description}, #{priority}, #{state}, #{type}, #{workbasketId}, #{owner})") + @Options(keyProperty = "id", keyColumn="ID") + void insert(Task task); + + @Update("UPDATE TASK SET TENANT_ID = #{tenantId}, CLAIMED = #{claimed}, COMPLETED = #{completed}, MODIFIED = #{modified}, PLANNED = #{planned}, DUE = #{due}, NAME = #{name}, DESCRIPTION = #{description}, PRIORITY = #{priority}, STATE = #{state}, TYPE = #{type}, WORKBASKETID = #{workbasketId}, OWNER = #{owner} WHERE ID = #{id}") + void update(Task task); + + @Delete("DELETE FROM TASK WHERE ID = #{id}") + void delete(String id); + +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/mappings/WorkbasketAccessMapper.java b/lib/taskana-core/src/main/java/org/taskana/model/mappings/WorkbasketAccessMapper.java new file mode 100644 index 000000000..f258eff62 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/mappings/WorkbasketAccessMapper.java @@ -0,0 +1,114 @@ +package org.taskana.model.mappings; + +import java.util.List; + +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; +import org.taskana.model.WorkbasketAccessItem; + +public interface WorkbasketAccessMapper { + + @Select("SELECT ID, WORKBASKET_ID, USER_ID, GROUP_ID, READ, OPEN, APPEND, TRANSFER, DISTRIBUTE FROM WORKBASKET_ACCESS_LIST WHERE ID = #{id}") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "workbasketId", column = "WORKBASKET_ID"), + @Result(property = "userId", column = "USER_ID"), + @Result(property = "groupId", column = "GROUP_ID"), + @Result(property = "read", column = "READ"), + @Result(property = "open", column = "OPEN"), + @Result(property = "append", column = "APPEND"), + @Result(property = "transfer", column = "TRANSFER"), + @Result(property = "distribute", column = "DISTRIBUTE")}) + WorkbasketAccessItem findById(@Param("id") String id); + + @Select("SELECT ID, WORKBASKET_ID, USER_ID, GROUP_ID, READ, OPEN, APPEND, TRANSFER, DISTRIBUTE FROM WORKBASKET_ACCESS_LIST WHERE USER_ID = #{userId}") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "workbasketId", column = "WORKBASKET_ID"), + @Result(property = "userId", column = "USER_ID"), + @Result(property = "groupId", column = "GROUP_ID"), + @Result(property = "read", column = "READ"), + @Result(property = "open", column = "OPEN"), + @Result(property = "append", column = "APPEND"), + @Result(property = "transfer", column = "TRANSFER"), + @Result(property = "distribute", column = "DISTRIBUTE")}) + List findByUserId(@Param("userId") String userId); + + @Select("SELECT ID, WORKBASKET_ID, USER_ID, GROUP_ID, READ, OPEN, APPEND, TRANSFER, DISTRIBUTE FROM WORKBASKET_ACCESS_LIST WHERE WORKBASKET_ID = #{id}") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "workbasketId", column = "WORKBASKET_ID"), + @Result(property = "userId", column = "USER_ID"), + @Result(property = "groupId", column = "GROUP_ID"), + @Result(property = "read", column = "READ"), + @Result(property = "open", column = "OPEN"), + @Result(property = "append", column = "APPEND"), + @Result(property = "transfer", column = "TRANSFER"), + @Result(property = "distribute", column = "DISTRIBUTE")}) + List findByWorkbasketId(@Param("id") String id); + + @Select("SELECT ID, WORKBASKET_ID, USER_ID, GROUP_ID, READ, OPEN, APPEND, TRANSFER, DISTRIBUTE FROM WORKBASKET_ACCESS_LIST ORDER BY ID") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "workbasketId", column = "WORKBASKET_ID"), + @Result(property = "userId", column = "USER_ID"), + @Result(property = "groupId", column = "GROUP_ID"), + @Result(property = "read", column = "READ"), + @Result(property = "open", column = "OPEN"), + @Result(property = "append", column = "APPEND"), + @Result(property = "transfer", column = "TRANSFER"), + @Result(property = "distribute", column = "DISTRIBUTE")}) + List findAll(); + + @Insert("INSERT INTO WORKBASKET_ACCESS_LIST (ID, WORKBASKET_ID, USER_ID, GROUP_ID, READ, OPEN, APPEND, TRANSFER, DISTRIBUTE) " + + "VALUES (#{workbasketAccessItem.id}, #{workbasketAccessItem.workbasketId}, #{workbasketAccessItem.userId}, #{workbasketAccessItem.groupId}, #{workbasketAccessItem.read}, #{workbasketAccessItem.open}, #{workbasketAccessItem.append}, #{workbasketAccessItem.transfer}, #{workbasketAccessItem.distribute})") + @Options(keyProperty = "id", keyColumn="ID") + void insert(@Param("workbasketAccessItem") WorkbasketAccessItem workbasketAccessItem); + + @Update("UPDATE WORKBASKET_ACCESS_LIST SET WORKBASKET_ID = #{workbasketAccessItem.workbasketId}, USER_ID = #{workbasketAccessItem.userId}, GROUP_ID = #{workbasketAccessItem.groupId}, READ = #{workbasketAccessItem.read}, OPEN = #{workbasketAccessItem.open}, APPEND = #{workbasketAccessItem.append}, TRANSFER = #{workbasketAccessItem.transfer}, DISTRIBUTE = #{workbasketAccessItem.distribute} " + + "WHERE id = #{workbasketAccessItem.id}") + void update(@Param("workbasketAccessItem") WorkbasketAccessItem workbasketAccessItem); + + @Delete("DELETE FROM WORKBASKET_ACCESS_LIST where id = #{id}") + void delete(@Param("id") String id); + + @Select("") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "workbasketId", column = "WORKBASKET_ID"), + @Result(property = "userId", column = "USER_ID"), + @Result(property = "groupId", column = "GROUP_ID"), + @Result(property = "read", column = "READ"), + @Result(property = "open", column = "OPEN"), + @Result(property = "append", column = "APPEND"), + @Result(property = "transfer", column = "TRANSFER"), + @Result(property = "distribute", column = "DISTRIBUTE")}) + List findByWorkbasketAndUserAndAuthorization(@Param("workbasketId") String workbasketId, @Param("userId") String userId, @Param("authorization") String authorization); + + @Select("SELECT ID, WORKBASKET_ID, USER_ID, GROUP_ID, READ, OPEN, APPEND, TRANSFER, DISTRIBUTE FROM WORKBASKET_ACCESS_LIST WHERE WORKBASKET_ID = #{workbasketId} AND GROUP_ID = #{groupId}") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "workbasketId", column = "WORKBASKET_ID"), + @Result(property = "userId", column = "USER_ID"), + @Result(property = "groupId", column = "GROUP_ID"), + @Result(property = "read", column = "READ"), + @Result(property = "open", column = "OPEN"), + @Result(property = "append", column = "APPEND"), + @Result(property = "transfer", column = "TRANSFER"), + @Result(property = "distribute", column = "DISTRIBUTE")}) + List findByWorkbasketAndGroup(@Param("workbasketId") String workbasketId, @Param("groupId") String groupId); +} diff --git a/lib/taskana-core/src/main/java/org/taskana/model/mappings/WorkbasketMapper.java b/lib/taskana-core/src/main/java/org/taskana/model/mappings/WorkbasketMapper.java new file mode 100644 index 000000000..33161ed52 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/model/mappings/WorkbasketMapper.java @@ -0,0 +1,65 @@ +package org.taskana.model.mappings; + +import java.util.List; + +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Many; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; +import org.apache.ibatis.mapping.FetchType; +import org.taskana.model.Workbasket; + +public interface WorkbasketMapper { + + @Select("SELECT ID, TENANT_ID, CREATED, MODIFIED, NAME, DESCRIPTION, OWNER FROM WORKBASKET WHERE ID = #{id}") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "tenantId", column = "TENANT_ID"), + @Result(property = "created", column = "CREATED"), + @Result(property = "modified", column = "MODIFIED"), + @Result(property = "name", column = "NAME"), + @Result(property = "description", column = "DESCRIPTION"), + @Result(property = "owner", column = "OWNER"), + @Result(property = "distributionTargets", column = "ID", javaType = List.class, many = @Many(fetchType = FetchType.DEFAULT, select="findByDistributionTargets")) }) + public Workbasket findById(@Param("id") String id); + + @Select("SELECT * FROM WORKBASKET WHERE id IN (SELECT TARGET_ID FROM DISTRIBUTION_TARGETS WHERE SOURCE_ID = #{id})") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "tenantId", column = "TENANT_ID"), + @Result(property = "created", column = "CREATED"), + @Result(property = "modified", column = "MODIFIED"), + @Result(property = "name", column = "NAME"), + @Result(property = "description", column = "DESCRIPTION"), + @Result(property = "owner", column = "OWNER"), + @Result(property = "distributionTargets", column = "ID", javaType = List.class, many = @Many(fetchType = FetchType.DEFAULT, select="findByDistributionTargets")) }) + public List findByDistributionTargets(@Param("id") String id); + + @Select("Select * FROM WORKBASKET ORDER BY id") + @Results(value = { + @Result(property = "id", column = "ID"), + @Result(property = "tenantId", column = "TENANT_ID"), + @Result(property = "created", column = "CREATED"), + @Result(property = "modified", column = "MODIFIED"), + @Result(property = "name", column = "NAME"), + @Result(property = "description", column = "DESCRIPTION"), + @Result(property = "owner", column = "OWNER"), + @Result(property = "distributionTargets", column = "ID", javaType = List.class, many = @Many(fetchType = FetchType.DEFAULT, select="findByDistributionTargets")) }) + public List findAll(); + + @Insert("INSERT INTO WORKBASKET (ID, TENANT_ID, CREATED, MODIFIED, NAME, DESCRIPTION, OWNER) VALUES (#{workbasket.id}, #{workbasket.tenantId}, #{workbasket.created}, #{workbasket.modified}, #{workbasket.name}, #{workbasket.description}, #{workbasket.owner})") + @Options(keyProperty = "id", keyColumn="ID") + public void insert(@Param("workbasket") Workbasket workbasket); + + @Update("UPDATE WORKBASKET SET TENANT_ID = #{workbasket.tenantId}, MODIFIED = #{workbasket.modified}, NAME = #{workbasket.name}, DESCRIPTION = #{workbasket.description}, OWNER = #{workbasket.owner} WHERE id = #{workbasket.id}") + public void update(@Param("workbasket") Workbasket workbasket); + + @Delete("DELETE FROM WORKBASKET where id = #{id}") + public void delete(@Param("id") String id); + +} \ No newline at end of file diff --git a/lib/taskana-core/src/main/java/org/taskana/security/CurrentUserContext.java b/lib/taskana-core/src/main/java/org/taskana/security/CurrentUserContext.java new file mode 100644 index 000000000..32735ccb2 --- /dev/null +++ b/lib/taskana-core/src/main/java/org/taskana/security/CurrentUserContext.java @@ -0,0 +1,47 @@ +package org.taskana.security; + +import java.security.AccessController; +import java.util.List; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides the context information about the current (calling) user. The + * context is gathered from the JAAS subject. + * + * @author Holger Hagen + * + */ +public class CurrentUserContext { + + private static final Logger logger = LoggerFactory.getLogger(CurrentUserContext.class); + + /** + * Returns the userid of the current user. + * + * @return String the userid. null if there is no JAAS subject. + */ + public static String getUserid() { + Subject subject = Subject.getSubject(AccessController.getContext()); + logger.debug("Subject of caller: {}", subject); + if (subject != null) { + Set publicCredentials = subject.getPublicCredentials(); + logger.debug("Public credentials of caller: {}", publicCredentials); + for (Object pC : publicCredentials) { + logger.debug("Returning the first public credential: {}", pC.toString()); + return pC.toString(); + } + } + logger.debug("No userid found in subject!"); + return null; + } + + public static List getGroupIds() { + return null; + } + +} diff --git a/lib/taskana-core/src/main/resources/log4j2-test.xml b/lib/taskana-core/src/main/resources/log4j2-test.xml new file mode 100644 index 000000000..db28892cb --- /dev/null +++ b/lib/taskana-core/src/main/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/taskana-core/src/main/resources/sql/db-structure.sql b/lib/taskana-core/src/main/resources/sql/db-structure.sql new file mode 100644 index 000000000..b67776862 --- /dev/null +++ b/lib/taskana-core/src/main/resources/sql/db-structure.sql @@ -0,0 +1,62 @@ +CREATE TABLE IF NOT EXISTS TASK( + ID varchar(255) NOT NULL, + TENANT_ID varchar(255) NULL, + CREATED TIMESTAMP NULL, + CLAIMED TIMESTAMP NULL, + COMPLETED TIMESTAMP NULL, + MODIFIED TIMESTAMP NULL, + PLANNED TIMESTAMP NULL, + DUE TIMESTAMP NULL, + NAME varchar(1024) NULL, + DESCRIPTION varchar(4096) NULL, + PRIORITY INT NULL, + STATE varchar(20) NULL, + TYPE varchar(255) NULL, + WORKBASKETID varchar(255) NULL, + OWNER varchar(255) NULL, + PRIMARY KEY (ID) +); + +CREATE TABLE IF NOT EXISTS WORKBASKET( + ID varchar(255) NOT NULL, + TENANT_ID varchar(255) NULL, + CREATED TIMESTAMP NULL, + MODIFIED TIMESTAMP NULL, + NAME varchar(255) NULL, + DESCRIPTION varchar(255) NULL, + OWNER varchar(255) NULL, + PRIMARY KEY (ID), + CONSTRAINT UC_NAME UNIQUE (NAME) +); + +CREATE TABLE IF NOT EXISTS DISTRIBUTION_TARGETS( + SOURCE_ID varchar(255) NOT NULL, + TARGET_ID varchar(255) NOT NULL, + PRIMARY KEY (SOURCE_ID, TARGET_ID) +); + +CREATE TABLE IF NOT EXISTS BUSINESS_CATEGORY( + ID varchar(255) NOT NULL, + TENANT_ID varchar(255) NULL, + PARENT_CATEGORY_ID varchar(255), + CREATED DATE NULL, + MODIFIED DATE NULL, + NAME varchar(255) NULL, + DESCRIPTION varchar(255) NULL, + PRIORITY INT NULL, + SERVICE_LEVEL varchar(255) NULL, + PRIMARY KEY (ID) +); + +CREATE TABLE IF NOT EXISTS WORKBASKET_ACCESS_LIST( + ID varchar(255) NOT NULL, + WORKBASKET_ID varchar(255) NOT NULL, + USER_ID varchar(255) NULL, + GROUP_ID varchar(255) NULL, + READ bit NOT NULL, + OPEN bit NOT NULL, + APPEND bit NOT NULL, + TRANSFER bit NOT NULL, + DISTRIBUTE bit NOT NULL, + PRIMARY KEY (ID) +); \ No newline at end of file diff --git a/lib/taskana-core/src/test/java/org/taskana/impl/CategoryServiceImplTest.java b/lib/taskana-core/src/test/java/org/taskana/impl/CategoryServiceImplTest.java new file mode 100644 index 000000000..70b69b803 --- /dev/null +++ b/lib/taskana-core/src/test/java/org/taskana/impl/CategoryServiceImplTest.java @@ -0,0 +1,79 @@ +package org.taskana.impl; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.taskana.CategoryService; +import org.taskana.TaskanaEngine; +import org.taskana.configuration.TaskanaEngineConfiguration; +import org.taskana.model.Category; + +import javax.security.auth.login.LoginException; +import java.io.FileNotFoundException; +import java.sql.SQLException; +import java.time.LocalDate; + +public class CategoryServiceImplTest { + static int counter = 0; + private CategoryService categoryService; + + @Before + public void setup() throws FileNotFoundException, SQLException, LoginException { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:workbasket-test-db" + counter++); + ds.setPassword("sa"); + ds.setUser("sa"); + TaskanaEngineConfiguration taskEngineConfiguration = new TaskanaEngineConfiguration(ds, false); + + TaskanaEngine te = taskEngineConfiguration.buildTaskanaEngine(); + categoryService = te.getCategoryService(); + } + + @Test + public void testInsertCategory() { + Category category = new Category(); + category.setId("0"); + categoryService.insertCategory(category); + + Assert.assertNotNull(categoryService.selectCategoryById(category.getId())); + } + + @Test + public void testFindAllCategories() { + Category category0 = new Category(); + category0.setId("0"); + category0.setParentCategoryId(""); + categoryService.insertCategory(category0); + Category category1 = new Category(); + category1.setId("1"); + category1.setParentCategoryId(""); + categoryService.insertCategory(category1); + + Assert.assertEquals(2, categoryService.selectCategories().size()); + } + + @Test + public void testFindByParentCategory() { + Category category0 = new Category(); + category0.setId("0"); + category0.setParentCategoryId("0"); + categoryService.insertCategory(category0); + Category category1 = new Category(); + category1.setId("1"); + category1.setParentCategoryId("0"); + categoryService.insertCategory(category1); + + Assert.assertEquals(2, categoryService.selectCategoriesByParentId("0").size()); + } + + @Test + public void testModifiedCategory() { + Category category = new Category(); + categoryService.insertCategory(category); + category.setDescription("TEST EVERYTHING"); + categoryService.updateCategory(category); + + Assert.assertEquals(category.getModified().toString(), LocalDate.now().toString()); + } +} diff --git a/lib/taskana-core/src/test/java/org/taskana/impl/TaskServiceImplTest.java b/lib/taskana-core/src/test/java/org/taskana/impl/TaskServiceImplTest.java new file mode 100644 index 000000000..6d410a418 --- /dev/null +++ b/lib/taskana-core/src/test/java/org/taskana/impl/TaskServiceImplTest.java @@ -0,0 +1,167 @@ +package org.taskana.impl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.taskana.TaskanaEngine; +import org.taskana.configuration.TaskanaEngineConfiguration; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.Task; +import org.taskana.model.TaskState; +import org.taskana.model.Workbasket; +import org.taskana.model.mappings.TaskMapper; + +@RunWith(MockitoJUnitRunner.class) +public class TaskServiceImplTest { + + @InjectMocks + TaskServiceImpl taskServiceImpl; + @Mock + TaskanaEngine taskanaEngine; + @Mock + TaskanaEngineConfiguration taskanaEngineConfiguration; + @Mock + WorkbasketServiceImpl workbasketServiceImpl; + @Mock + TaskMapper taskMapper; + + @Test + public void testCreateSimpleTask() throws NotAuthorizedException { + registerBasicMocks(false); + Mockito.doNothing().when(workbasketServiceImpl).checkPermission(any(), any()); + Mockito.doNothing().when(taskMapper).insert(any()); + + Task task = new Task(); + task.setName("Unit Test Task"); + task.setWorkbasketId("1"); + task = taskServiceImpl.create(task); + Assert.assertNull(task.getOwner()); + Assert.assertNotNull(task.getCreated()); + Assert.assertNotNull(task.getModified()); + Assert.assertNull(task.getCompleted()); + Assert.assertEquals(task.getWorkbasketId(), "1"); + Assert.assertEquals(task.getState(), TaskState.READY); + } + + @Test + public void testClaim() throws Exception { + Task task = createUnitTestTask("1", "Unit Test Task 1", "1"); + + Thread.sleep(100); // to have different timestamps + taskServiceImpl.claim(task.getId(), "John Does"); + task = taskServiceImpl.getTaskById(task.getId()); + Assert.assertEquals(task.getState(), TaskState.CLAIMED); + Assert.assertNotEquals(task.getCreated(), task.getModified()); + Assert.assertNotNull(task.getClaimed()); + Assert.assertEquals(task.getOwner(), "John Does"); + } + + @Test(expected = TaskNotFoundException.class) + public void testClaimFailsWithNonExistingTaskId() throws TaskNotFoundException { + taskServiceImpl.claim("test", "John Doe"); + } + + @Test + public void testComplete() throws Exception { + Task task = createUnitTestTask("1", "Unit Test Task 1", "1"); + + Thread.sleep(100); // to have different timestamps + taskServiceImpl.complete(task.getId()); + task = taskServiceImpl.getTaskById(task.getId()); + Assert.assertEquals(task.getState(), TaskState.COMPLETED); + Assert.assertNotEquals(task.getCreated(), task.getModified()); + Assert.assertNotNull(task.getCompleted()); + } + + @Test(expected = TaskNotFoundException.class) + public void testCompleteFailsWithNonExistingTaskId() throws TaskNotFoundException { + taskServiceImpl.complete("test"); + } + + @Test + public void testGetTasksForWorkbasket() throws NotAuthorizedException { + registerBasicMocks(false); + ArrayList tasks = new ArrayList(); + tasks.add(createUnitTestTask("1", "Unit Test Task 1", "1")); + tasks.add(createUnitTestTask("2", "Unit Test Task 2", "1")); + tasks.add(createUnitTestTask("3", "Unit Test Task 3", "1")); + Mockito.when(taskMapper.findByWorkBasketId("1")).thenReturn(tasks); + + List list = taskServiceImpl.getTasksForWorkbasket("1"); + Assert.assertEquals(list.size(), 3); + } + + @Test + public void testTransferTaskZuDestinationWorkbasket() throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException { + registerBasicMocks(false); + Workbasket workbasket2 = createWorkbasket2(); + Mockito.when(workbasketServiceImpl.getWorkbasket("2")).thenReturn(workbasket2); + + Task task = createUnitTestTask("1", "Unit Test Task 1", "1"); + + Assert.assertEquals(taskServiceImpl.getTaskById(task.getId()).getWorkbasketId(), "1"); + taskServiceImpl.transfer(task.getId(), "2"); + Assert.assertEquals(taskServiceImpl.getTaskById(task.getId()).getWorkbasketId(), "2"); + } + + @Test(expected = WorkbasketNotFoundException.class) + public void testTransferFailsIfDestinationWorkbasketDoesNotExist_withSecurityDisabled() throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException { + registerBasicMocks(false); + Mockito.doThrow(WorkbasketNotFoundException.class).when(workbasketServiceImpl).checkPermission(eq("invalidWorkbasketId"), any()); + + Task task = createUnitTestTask("1", "Unit Test Task 1", "1"); + + Assert.assertEquals(taskServiceImpl.getTaskById(task.getId()).getWorkbasketId(), "1"); + taskServiceImpl.transfer(task.getId(), "invalidWorkbasketId"); + } + + @Test(expected = WorkbasketNotFoundException.class) + public void testTransferFailsIfDestinationWorkbasketDoesNotExist_withSecurityEnabled() throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException { + registerBasicMocks(true); + Mockito.doThrow(WorkbasketNotFoundException.class).when(workbasketServiceImpl).checkPermission(eq("invalidWorkbasketId"), any()); + + Task task = createUnitTestTask("1", "Unit Test Task 1", "1"); + + Assert.assertEquals(taskServiceImpl.getTaskById(task.getId()).getWorkbasketId(), "1"); + taskServiceImpl.transfer(task.getId(), "invalidWorkbasketId"); + } + + private Task createUnitTestTask(String id, String name, String workbasketId) { + Task task = new Task(); + task.setId(id); + task.setName(name); + task.setWorkbasketId(workbasketId); + Timestamp now = new Timestamp(System.currentTimeMillis()); + task.setCreated(now); + task.setModified(now); + Mockito.when(taskMapper.findById(any())).thenReturn(task); + return task; + } + + private Workbasket createWorkbasket2() { + Workbasket workbasket2 = new Workbasket(); + workbasket2.setId("2"); + workbasket2.setName("Workbasket 2"); + return workbasket2; + } + + private void registerBasicMocks(boolean securityEnabled) { + Mockito.when(taskanaEngine.getConfiguration()).thenReturn(taskanaEngineConfiguration); + Mockito.when(taskanaEngineConfiguration.isSecurityEnabled()).thenReturn(securityEnabled); + Mockito.when(taskanaEngine.getWorkbasketService()).thenReturn(workbasketServiceImpl); + } + +} diff --git a/lib/taskana-core/src/test/java/org/taskana/impl/TaskServiceImplTransactionTest.java b/lib/taskana-core/src/test/java/org/taskana/impl/TaskServiceImplTransactionTest.java new file mode 100644 index 000000000..381a8780c --- /dev/null +++ b/lib/taskana-core/src/test/java/org/taskana/impl/TaskServiceImplTransactionTest.java @@ -0,0 +1,80 @@ +package org.taskana.impl; + +import java.io.FileNotFoundException; +import java.sql.SQLException; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.Assert; +import org.junit.Test; +import org.taskana.TaskanaEngine; +import org.taskana.configuration.TaskanaEngineConfiguration; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.model.Task; + +public class TaskServiceImplTransactionTest { + + @Test + public void testStart() throws FileNotFoundException, SQLException, TaskNotFoundException, NotAuthorizedException { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:workbasket-test-db45"); + ds.setPassword("sa"); + ds.setUser("sa"); + TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(ds, true, false); + + TaskanaEngineImpl te = (TaskanaEngineImpl) taskanaEngineConfiguration.buildTaskanaEngine(); + TaskServiceImpl taskServiceImpl = (TaskServiceImpl) te.getTaskService(); + + Task task = new Task(); + task.setName("Unit Test Task"); + task.setWorkbasketId("1"); + task = taskServiceImpl.create(task); + te.closeSession(); + + TaskanaEngine te2 = taskanaEngineConfiguration.buildTaskanaEngine(); + TaskServiceImpl taskServiceImpl2 = (TaskServiceImpl) te2.getTaskService(); + Task resultTask = taskServiceImpl2.getTaskById(task.getId()); + + Assert.assertNotNull(resultTask); + } + + @Test(expected = TaskNotFoundException.class) + public void testStartTransactionFail() + throws FileNotFoundException, SQLException, TaskNotFoundException, NotAuthorizedException { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:workbasket-test-db46"); + ds.setPassword("sa"); + ds.setUser("sa"); + TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(ds, false, false); + + TaskanaEngineImpl te = (TaskanaEngineImpl) taskanaEngineConfiguration.buildTaskanaEngine(); + TaskServiceImpl taskServiceImpl = (TaskServiceImpl) te.getTaskService(); + + Task task = new Task(); + task.setName("Unit Test Task"); + task.setWorkbasketId("1"); + task = taskServiceImpl.create(task); + taskServiceImpl.getTaskById("1"); + te.closeSession(); + + TaskanaEngineImpl te2 = (TaskanaEngineImpl) taskanaEngineConfiguration.buildTaskanaEngine(); + TaskServiceImpl taskServiceImpl2 = (TaskServiceImpl) te2.getTaskService(); + taskServiceImpl2.getTaskById("1"); + } + + @Test + public void testCreateTaskInTaskanaWithDefaultDb() + throws FileNotFoundException, SQLException, TaskNotFoundException, NotAuthorizedException { + TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(null, false, false); + TaskanaEngine te = taskanaEngineConfiguration.buildTaskanaEngine(); + TaskServiceImpl taskServiceImpl = (TaskServiceImpl) te.getTaskService(); + + Task task = new Task(); + task.setName("Unit Test Task"); + task.setWorkbasketId("1"); + task = taskServiceImpl.create(task); + + Assert.assertNotNull(task); + Assert.assertNotNull(task.getId()); + } +} diff --git a/lib/taskana-core/src/test/java/org/taskana/impl/WorkbasketServiceImplTest.java b/lib/taskana-core/src/test/java/org/taskana/impl/WorkbasketServiceImplTest.java new file mode 100644 index 000000000..1468b25f9 --- /dev/null +++ b/lib/taskana-core/src/test/java/org/taskana/impl/WorkbasketServiceImplTest.java @@ -0,0 +1,161 @@ +package org.taskana.impl; + +import java.io.FileNotFoundException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.login.LoginException; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.taskana.TaskanaEngine; +import org.taskana.WorkbasketService; +import org.taskana.configuration.TaskanaEngineConfiguration; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.Workbasket; +import org.taskana.model.WorkbasketAccessItem; + +public class WorkbasketServiceImplTest { + + WorkbasketService workbasketServiceImpl; + static int counter = 0; + + @Before + public void setup() throws FileNotFoundException, SQLException, LoginException { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:workbasket-test-db2" + counter++); + ds.setPassword("sa"); + ds.setUser("sa"); + TaskanaEngineConfiguration taskEngineConfiguration = new TaskanaEngineConfiguration(ds, false); + + TaskanaEngine te = taskEngineConfiguration.buildTaskanaEngine(); + workbasketServiceImpl = te.getWorkbasketService(); + } + + @Test + public void testInsertWorkbasket() throws NotAuthorizedException { + int before = workbasketServiceImpl.getWorkbaskets().size(); + Workbasket workbasket = new Workbasket(); + workbasket.setId("1"); + workbasketServiceImpl.createWorkbasket(workbasket); + Assert.assertEquals(before + 1, workbasketServiceImpl.getWorkbaskets().size()); + } + + @Test + public void testSelectAllWorkbaskets() throws NotAuthorizedException { + int before = workbasketServiceImpl.getWorkbaskets().size(); + Workbasket workbasket0 = new Workbasket(); + workbasket0.setId("0"); + workbasketServiceImpl.createWorkbasket(workbasket0); + Workbasket workbasket1 = new Workbasket(); + workbasket1.setId("1"); + workbasketServiceImpl.createWorkbasket(workbasket1); + Workbasket workbasket2 = new Workbasket(); + workbasket2.setId("2"); + workbasketServiceImpl.createWorkbasket(workbasket2); + Assert.assertEquals(before + 3, workbasketServiceImpl.getWorkbaskets().size()); + } + + @Test + public void testSelectWorkbasket() throws WorkbasketNotFoundException, NotAuthorizedException { + Workbasket workbasket0 = new Workbasket(); + workbasket0.setId("0"); + workbasketServiceImpl.createWorkbasket(workbasket0); + Workbasket workbasket1 = new Workbasket(); + workbasket1.setId("1"); + workbasketServiceImpl.createWorkbasket(workbasket1); + Workbasket workbasket2 = new Workbasket(); + workbasket2.setId("2"); + workbasketServiceImpl.createWorkbasket(workbasket2); + Workbasket foundWorkbasket = workbasketServiceImpl.getWorkbasket("2"); + Assert.assertEquals("2", foundWorkbasket.getId()); + } + + @Test(expected = WorkbasketNotFoundException.class) + public void testGetWorkbasketFail() throws WorkbasketNotFoundException { + workbasketServiceImpl.getWorkbasket("fail"); + } + + @Test + public void testSelectWorkbasketWithDistribution() throws WorkbasketNotFoundException, NotAuthorizedException { + Workbasket workbasket0 = new Workbasket(); + workbasket0.setId("0"); + Workbasket workbasket1 = new Workbasket(); + workbasket1.setId("1"); + Workbasket workbasket2 = new Workbasket(); + workbasket2.setId("2"); + workbasket2.setDistributionTargets(new ArrayList<>()); + workbasket2.getDistributionTargets().add(workbasket0); + workbasket2.getDistributionTargets().add(workbasket1); + workbasketServiceImpl.createWorkbasket(workbasket2); + Workbasket foundWorkbasket = workbasketServiceImpl.getWorkbasket("2"); + Assert.assertEquals("2", foundWorkbasket.getId()); + Assert.assertEquals(2, foundWorkbasket.getDistributionTargets().size()); + } + + @Test + public void testUpdateWorkbasket() throws Exception { + Workbasket workbasket0 = new Workbasket(); + workbasket0.setId("0"); + Workbasket workbasket1 = new Workbasket(); + workbasket1.setId("1"); + Workbasket workbasket2 = new Workbasket(); + workbasket2.setId("2"); + workbasket2.getDistributionTargets().add(workbasket0); + workbasket2.getDistributionTargets().add(workbasket1); + workbasketServiceImpl.createWorkbasket(workbasket2); + + Workbasket workbasket3 = new Workbasket(); + workbasket3.setId("3"); + workbasket2.getDistributionTargets().clear(); + workbasket2.getDistributionTargets().add(workbasket3); + Thread.sleep(100); + workbasketServiceImpl.updateWorkbasket(workbasket2); + + Workbasket foundBasket = workbasketServiceImpl.getWorkbasket(workbasket2.getId()); + + List distributionTargets = foundBasket.getDistributionTargets(); + Assert.assertEquals(1, distributionTargets.size()); + Assert.assertEquals("3", distributionTargets.get(0).getId()); + Assert.assertNotEquals(workbasketServiceImpl.getWorkbasket("2").getCreated(), + workbasketServiceImpl.getWorkbasket("2").getModified()); + Assert.assertEquals(workbasketServiceImpl.getWorkbasket("1").getCreated(), + workbasketServiceImpl.getWorkbasket("1").getModified()); + Assert.assertEquals(workbasketServiceImpl.getWorkbasket("3").getCreated(), + workbasketServiceImpl.getWorkbasket("3").getModified()); + } + + @Test + public void testInsertWorkbasketAccessUser() throws NotAuthorizedException { + WorkbasketAccessItem accessItem = new WorkbasketAccessItem(); + accessItem.setWorkbasketId("1"); + accessItem.setUserId("Arthur Dent"); + accessItem.setOpen(true); + accessItem.setRead(true); + workbasketServiceImpl.createWorkbasketAuthorization(accessItem); + + Assert.assertEquals(1, workbasketServiceImpl.getAllAuthorizations().size()); + } + + @Test + public void testUpdateWorkbasketAccessUser() throws NotAuthorizedException { + WorkbasketAccessItem accessItem = new WorkbasketAccessItem(); + accessItem.setWorkbasketId("1"); + accessItem.setUserId("Arthur Dent"); + accessItem.setOpen(true); + accessItem.setRead(true); + workbasketServiceImpl.createWorkbasketAuthorization(accessItem); + + Assert.assertEquals(1, workbasketServiceImpl.getAllAuthorizations().size()); + + accessItem.setUserId("Zaphod Beeblebrox"); + workbasketServiceImpl.updateWorkbasketAuthorization(accessItem); + + Assert.assertEquals("Zaphod Beeblebrox", workbasketServiceImpl.getWorkbasketAuthorization(accessItem.getId()).getUserId()); + } + +} diff --git a/lib/taskana-core/src/test/java/org/taskana/impl/configuration/TaskanaEngineConfigurationTest.java b/lib/taskana-core/src/test/java/org/taskana/impl/configuration/TaskanaEngineConfigurationTest.java new file mode 100644 index 000000000..def44e0ed --- /dev/null +++ b/lib/taskana-core/src/test/java/org/taskana/impl/configuration/TaskanaEngineConfigurationTest.java @@ -0,0 +1,28 @@ +package org.taskana.impl.configuration; + +import java.io.FileNotFoundException; +import java.sql.SQLException; + +import javax.security.auth.login.LoginException; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.Assert; +import org.junit.Test; +import org.taskana.TaskanaEngine; +import org.taskana.configuration.TaskanaEngineConfiguration; + +public class TaskanaEngineConfigurationTest { + + @Test + public void testCreateTaskEngine() throws FileNotFoundException, SQLException, LoginException { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:workbasket-test-db"); + ds.setPassword("sa"); + ds.setUser("sa"); + TaskanaEngineConfiguration taskEngineConfiguration = new TaskanaEngineConfiguration(ds, false); + + TaskanaEngine te = taskEngineConfiguration.buildTaskanaEngine(); + + Assert.assertNotNull(te); + } +} diff --git a/lib/taskana-core/src/test/resources/log4j2-test.xml b/lib/taskana-core/src/test/resources/log4j2-test.xml new file mode 100644 index 000000000..db28892cb --- /dev/null +++ b/lib/taskana-core/src/test/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/taskana-spring-example/.gitignore b/lib/taskana-spring-example/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/lib/taskana-spring-example/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/lib/taskana-spring-example/pom.xml b/lib/taskana-spring-example/pom.xml new file mode 100644 index 000000000..db98be019 --- /dev/null +++ b/lib/taskana-spring-example/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + org.taskana + taskana-spring-example + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.2.RELEASE + + + + + 1.8 + 1.8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework + spring-context + + + org.springframework + spring-jdbc + + + org.springframework + spring-tx + + + + org.taskana + taskana-core + 0.0.1-SNAPSHOT + + + org.taskana + taskana-spring + 0.0.1-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/lib/taskana-spring-example/src/main/java/org/taskana/ExampleBootstrap.java b/lib/taskana-spring-example/src/main/java/org/taskana/ExampleBootstrap.java new file mode 100644 index 000000000..6f94d4ccd --- /dev/null +++ b/lib/taskana-spring-example/src/main/java/org/taskana/ExampleBootstrap.java @@ -0,0 +1,34 @@ +package org.taskana; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.model.Task; + +@Component +@Transactional +public class ExampleBootstrap { + + @Autowired + private TaskService taskService; + + @PostConstruct + public void test() throws TaskNotFoundException, NotAuthorizedException { + System.out.println("---------------------------> Start App"); + Task task = new Task(); + task.setName("Spring example task"); + task.setWorkbasketId("1"); + task = taskService.create(task); + System.out.println("---------------------------> Task started: " + task.getId()); + taskService.claim(task.getId(), "John Doe"); + System.out.println( + "---------------------------> Task claimed: " + taskService.getTaskById(task.getId()).getOwner()); + // taskService.complete(task.getId()); + // System.out.println("---------------------------> Task completed"); + } + +} diff --git a/lib/taskana-spring-example/src/main/java/org/taskana/SpringBootWebApplication.java b/lib/taskana-spring-example/src/main/java/org/taskana/SpringBootWebApplication.java new file mode 100644 index 000000000..c79af108d --- /dev/null +++ b/lib/taskana-spring-example/src/main/java/org/taskana/SpringBootWebApplication.java @@ -0,0 +1,22 @@ +package org.taskana; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ImportResource; + +@SpringBootApplication +@ImportResource("applicationContext.xml") +public class SpringBootWebApplication extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(SpringBootWebApplication.class); + } + + public static void main(String[] args) throws Exception { + SpringApplication.run(SpringBootWebApplication.class, args); + } + +} \ No newline at end of file diff --git a/lib/taskana-spring-example/src/main/resources/applicationContext.xml b/lib/taskana-spring-example/src/main/resources/applicationContext.xml new file mode 100644 index 000000000..3229062bf --- /dev/null +++ b/lib/taskana-spring-example/src/main/resources/applicationContext.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/taskana-spring/.gitignore b/lib/taskana-spring/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/lib/taskana-spring/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/lib/taskana-spring/pom.xml b/lib/taskana-spring/pom.xml new file mode 100644 index 000000000..aab2e7e1b --- /dev/null +++ b/lib/taskana-spring/pom.xml @@ -0,0 +1,60 @@ + + 4.0.0 + org.taskana + taskana-spring + 0.0.1-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 1.4.2.RELEASE + + + + + 1.8 + 1.8 + UTF-8 + + + + + org.taskana + taskana-core + 0.0.1-SNAPSHOT + + + + org.springframework + spring-tx + + + + + org.springframework.boot + spring-boot-starter-web + test + + + org.springframework + spring-context + test + + + org.springframework + spring-jdbc + test + + + org.springframework.boot + spring-boot-starter-test + test + + + com.h2database + h2 + test + + + \ No newline at end of file diff --git a/lib/taskana-spring/src/main/java/org/taskana/SpringTaskanaEngineImpl.java b/lib/taskana-spring/src/main/java/org/taskana/SpringTaskanaEngineImpl.java new file mode 100644 index 000000000..49dbfece6 --- /dev/null +++ b/lib/taskana-spring/src/main/java/org/taskana/SpringTaskanaEngineImpl.java @@ -0,0 +1,33 @@ +package org.taskana; + +import java.sql.SQLException; + +import javax.annotation.PostConstruct; + +import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; +import org.springframework.transaction.PlatformTransactionManager; +import org.taskana.configuration.SpringTaskanaEngineConfiguration; +import org.taskana.impl.TaskanaEngineImpl; + +public class SpringTaskanaEngineImpl extends TaskanaEngineImpl { + + public SpringTaskanaEngineImpl(SpringTaskanaEngineConfiguration taskanaEngineConfiguration) { + super(taskanaEngineConfiguration); + } + + private PlatformTransactionManager transactionManager; + + @PostConstruct + public void init() throws SQLException { + this.transactionFactory = new ManagedTransactionFactory(); + } + + public PlatformTransactionManager getTransactionManager() { + return transactionManager; + } + + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + +} diff --git a/lib/taskana-spring/src/main/java/org/taskana/configuration/SpringTaskanaEngineConfiguration.java b/lib/taskana-spring/src/main/java/org/taskana/configuration/SpringTaskanaEngineConfiguration.java new file mode 100644 index 000000000..66d039494 --- /dev/null +++ b/lib/taskana-spring/src/main/java/org/taskana/configuration/SpringTaskanaEngineConfiguration.java @@ -0,0 +1,34 @@ +package org.taskana.configuration; + +import java.sql.SQLException; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.taskana.SpringTaskanaEngineImpl; +import org.taskana.TaskanaEngine; + +public class SpringTaskanaEngineConfiguration extends TaskanaEngineConfiguration { + + @PostConstruct + public void init() throws SQLException { + dbScriptRunner = new DbScriptRunner(this.dataSource); + dbScriptRunner.run(); + } + + /** + * This method creates the Spring-based TaskanaEngine without an + * sqlSessionFactory + * + * @return the TaskanaEngine + * @throws SQLException + */ + public TaskanaEngine buildTaskanaEngine() throws SQLException { + return new SpringTaskanaEngineImpl(this); + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + +} diff --git a/lib/taskana-spring/src/test/java/org/taskana/TaskanaComponent.java b/lib/taskana-spring/src/test/java/org/taskana/TaskanaComponent.java new file mode 100644 index 000000000..975f538c8 --- /dev/null +++ b/lib/taskana-spring/src/test/java/org/taskana/TaskanaComponent.java @@ -0,0 +1,27 @@ +package org.taskana; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.model.Task; + +@Component +@Transactional +public class TaskanaComponent { + + @Autowired + TaskService taskService; + + public TaskService getTaskService() { + return taskService; + } + + public void triggerRollback() throws NotAuthorizedException { + Task task = new Task(); + task.setName("Unit Test Task"); + task.setWorkbasketId("1"); + task = taskService.create(task); + throw new RuntimeException(); + } +} diff --git a/lib/taskana-spring/src/test/java/org/taskana/TransactionTest.java b/lib/taskana-spring/src/test/java/org/taskana/TransactionTest.java new file mode 100644 index 000000000..c82cda210 --- /dev/null +++ b/lib/taskana-spring/src/test/java/org/taskana/TransactionTest.java @@ -0,0 +1,83 @@ +package org.taskana; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ContextConfiguration("classpath:test-applicationContext.xml") +@EnableAutoConfiguration +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class TransactionTest { + + @Autowired + TaskService taskService; + + @Autowired + private TestRestTemplate restTemplate; + + @LocalServerPort + int port; + + @Before + public void init() throws SQLException, ClassNotFoundException { + Class.forName("org.h2.Driver"); + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:task-engine", "SA", "SA")) { + conn.createStatement().executeUpdate("DELETE FROM TASK WHERE 1=1"); + conn.commit(); + } + } + + @Test + @Ignore + public void testCommit() throws SQLException { + restTemplate.getForEntity("http://127.0.0.1:" + port + "/test", String.class); + + int resultCount = 0; + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:task-engine", "SA", "SA")) { + ResultSet rs = conn.createStatement().executeQuery("SELECT ID FROM TASK"); + + while (rs.next()) { + resultCount++; + } + } + + Assert.assertEquals(1, resultCount); + } + + @Test + @Ignore + public void testRollback() throws SQLException { + restTemplate.postForEntity("http://127.0.0.1:" + port + "/test", null, String.class); + + int resultCount = 0; + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:task-engine", "SA", "SA")) { + ResultSet rs = conn.createStatement().executeQuery("SELECT ID FROM TASK"); + + while (rs.next()) { + resultCount++; + } + } + + Assert.assertEquals(0, resultCount); + } + +} diff --git a/lib/taskana-spring/src/test/resources/test-applicationContext.xml b/lib/taskana-spring/src/test/resources/test-applicationContext.xml new file mode 100644 index 000000000..0372ed5c6 --- /dev/null +++ b/lib/taskana-spring/src/test/resources/test-applicationContext.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 000000000..4183b6b94 --- /dev/null +++ b/manifest.yml @@ -0,0 +1,24 @@ +# Configuration file for Cloud Foundry, see https://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html +applications: +- name: taskana-rest + path: rest/target/rest-0.0.1-SNAPSHOT.jar + buildpack: https://github.com/cloudfoundry/java-buildpack.git#v3.10 + memory: 512M + disk_quota: 256M +- name: taskana-workplace + path: workplace/dist + buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git#v1.3.16 + memory: 16M + disk_quota: 64M +- name: taskana-admin + path: admin/dist + buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git#v1.3.16 + memory: 16M + disk_quota: 64M +- name: taskana-monitor + path: monitor/dist + buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git#v1.3.16 + memory: 16M + disk_quota: 64M + + diff --git a/monitor/.angular-cli.json b/monitor/.angular-cli.json new file mode 100644 index 000000000..02b9980da --- /dev/null +++ b/monitor/.angular-cli.json @@ -0,0 +1,58 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "monitor" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "../node_modules/bootstrap/dist/css/bootstrap.min.css", + "styles.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/monitor/.editorconfig b/monitor/.editorconfig new file mode 100644 index 000000000..6e87a003d --- /dev/null +++ b/monitor/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/monitor/.gitignore b/monitor/.gitignore new file mode 100644 index 000000000..54bfd2001 --- /dev/null +++ b/monitor/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/monitor/README.md b/monitor/README.md new file mode 100644 index 000000000..9a300ddd0 --- /dev/null +++ b/monitor/README.md @@ -0,0 +1,28 @@ +# Monitor + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.3. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Before running the tests make sure you are serving the app via `ng serve`. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/monitor/e2e/app.e2e-spec.ts b/monitor/e2e/app.e2e-spec.ts new file mode 100644 index 000000000..b0d3d87aa --- /dev/null +++ b/monitor/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { MonitorPage } from './app.po'; + +describe('monitor App', () => { + let page: MonitorPage; + + beforeEach(() => { + page = new MonitorPage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/monitor/e2e/app.po.ts b/monitor/e2e/app.po.ts new file mode 100644 index 000000000..aac7dbb19 --- /dev/null +++ b/monitor/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class MonitorPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/monitor/e2e/tsconfig.e2e.json b/monitor/e2e/tsconfig.e2e.json new file mode 100644 index 000000000..e2a9a2fc7 --- /dev/null +++ b/monitor/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/monitor/karma.conf.js b/monitor/karma.conf.js new file mode 100644 index 000000000..84b4cd5ac --- /dev/null +++ b/monitor/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/monitor/package.json b/monitor/package.json new file mode 100644 index 000000000..32af87e97 --- /dev/null +++ b/monitor/package.json @@ -0,0 +1,50 @@ +{ + "name": "monitor", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/common": "^4.0.0", + "@angular/compiler": "^4.0.0", + "@angular/core": "^4.0.0", + "@angular/forms": "^4.0.0", + "@angular/http": "^4.0.0", + "@angular/platform-browser": "^4.0.0", + "@angular/platform-browser-dynamic": "^4.0.0", + "@angular/router": "^4.0.0", + "bootstrap": "^3.3.7", + "chart.js": "^2.5.0", + "core-js": "^2.4.1", + "ng2-charts": "^1.5.0", + "ngx-bootstrap": "^1.6.6", + "rxjs": "^5.1.0", + "zone.js": "^0.8.4" + }, + "devDependencies": { + "@angular/cli": "1.0.3", + "@angular/compiler-cli": "^4.0.0", + "@types/jasmine": "2.5.38", + "@types/node": "~6.0.60", + "codelyzer": "~2.0.0", + "jasmine-core": "~2.5.2", + "jasmine-spec-reporter": "~3.2.0", + "karma": "~1.4.1", + "karma-chrome-launcher": "~2.1.1", + "karma-cli": "~1.0.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-coverage-istanbul-reporter": "^0.2.0", + "protractor": "~5.1.0", + "ts-node": "~2.0.0", + "tslint": "~4.5.0", + "typescript": "~2.2.0" + } +} diff --git a/monitor/protractor.conf.js b/monitor/protractor.conf.js new file mode 100644 index 000000000..1c5e1e5a4 --- /dev/null +++ b/monitor/protractor.conf.js @@ -0,0 +1,30 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/monitor/src/app/app.component.css b/monitor/src/app/app.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/monitor/src/app/app.component.html b/monitor/src/app/app.component.html new file mode 100644 index 000000000..64bf7dced --- /dev/null +++ b/monitor/src/app/app.component.html @@ -0,0 +1,24 @@ + +
+ + + + + + + + + +
\ No newline at end of file diff --git a/monitor/src/app/app.component.spec.ts b/monitor/src/app/app.component.spec.ts new file mode 100644 index 000000000..c740bcd74 --- /dev/null +++ b/monitor/src/app/app.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + 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 works!'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app works!'); + })); + + 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('app works!'); + })); +}); diff --git a/monitor/src/app/app.component.ts b/monitor/src/app/app.component.ts new file mode 100644 index 000000000..427aadfec --- /dev/null +++ b/monitor/src/app/app.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { environment } from '../environments/environment'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + + adminUrl: string = environment.taskanaAdminUrl; + workplaceUrl: string = environment.taskanaWorkplaceUrl; +} diff --git a/monitor/src/app/app.module.ts b/monitor/src/app/app.module.ts new file mode 100644 index 000000000..597f6f71c --- /dev/null +++ b/monitor/src/app/app.module.ts @@ -0,0 +1,30 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { AlertModule } from 'ngx-bootstrap'; +import { ChartsModule } from 'ng2-charts'; +import { TabsModule } from 'ngx-bootstrap/tabs'; + +import { AppComponent } from './app.component'; +import { TasksComponent } from './tasks/tasks.component'; +import { WorkbasketComponent } from './workbasket/workbasket.component'; + +@NgModule({ + declarations: [ + AppComponent, + TasksComponent, + WorkbasketComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule, + AlertModule.forRoot(), + ChartsModule, + TabsModule.forRoot() + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/monitor/src/app/model/state.ts b/monitor/src/app/model/state.ts new file mode 100644 index 000000000..6f8fd7bb3 --- /dev/null +++ b/monitor/src/app/model/state.ts @@ -0,0 +1,4 @@ +export class State { + state: string; + counter: number; +} \ No newline at end of file diff --git a/monitor/src/app/model/workbasket-counter-data.ts b/monitor/src/app/model/workbasket-counter-data.ts new file mode 100644 index 000000000..1ab6d53ea --- /dev/null +++ b/monitor/src/app/model/workbasket-counter-data.ts @@ -0,0 +1,4 @@ +export class WorkbasketCounterData { + data: Array; + label: string; +} \ No newline at end of file diff --git a/monitor/src/app/model/workbasket-counter.ts b/monitor/src/app/model/workbasket-counter.ts new file mode 100644 index 000000000..e8a55a24e --- /dev/null +++ b/monitor/src/app/model/workbasket-counter.ts @@ -0,0 +1,6 @@ +import { WorkbasketCounterData } from './workbasket-counter-data'; + +export class WorkbasketCounter { + dates: Array[]; + data: Array; +} \ No newline at end of file diff --git a/monitor/src/app/service/rest-connector.service.spec.ts b/monitor/src/app/service/rest-connector.service.spec.ts new file mode 100644 index 000000000..e8e748bbf --- /dev/null +++ b/monitor/src/app/service/rest-connector.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { RestConnectorService } from './rest-connector.service'; + +describe('RestConnectorService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RestConnectorService] + }); + }); + + it('should be created', inject([RestConnectorService], (service: RestConnectorService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/monitor/src/app/service/rest-connector.service.ts b/monitor/src/app/service/rest-connector.service.ts new file mode 100644 index 000000000..2a5805f31 --- /dev/null +++ b/monitor/src/app/service/rest-connector.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { RequestOptions, Headers, Http, Response } from '@angular/http'; +import { environment } from '../../environments/environment'; +import { Observable } from 'rxjs/Observable'; +import { State } from '../model/state'; +import { WorkbasketCounter } from '../model/workbasket-counter'; + +@Injectable() +export class RestConnectorService { + + constructor(private http: Http) { } + + getTaskStatistics(): Observable { + return this.http.get(environment.taskanaRestUrl + "/v1/monitor/countByState?states=READY,CLAIMED,COMPLETED", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + + getWorkbasketStatistics(): Observable { + return this.http.get(environment.taskanaRestUrl + "/v1/monitor/taskcountByWorkbasketDaysAndState?daysInPast=5&states=READY,CLAIMED,COMPLETED", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + private createAuthorizationHeader() { + let headers: Headers = new Headers(); + headers.append("Authorization", "Basic TWF4OnRlc3Q="); + + return new RequestOptions({ headers: headers }); + } +} diff --git a/monitor/src/app/tasks/tasks.component.css b/monitor/src/app/tasks/tasks.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/monitor/src/app/tasks/tasks.component.html b/monitor/src/app/tasks/tasks.component.html new file mode 100644 index 000000000..20ac48f0f --- /dev/null +++ b/monitor/src/app/tasks/tasks.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/monitor/src/app/tasks/tasks.component.spec.ts b/monitor/src/app/tasks/tasks.component.spec.ts new file mode 100644 index 000000000..88f4f05f3 --- /dev/null +++ b/monitor/src/app/tasks/tasks.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TasksComponent } from './tasks.component'; + +describe('TasksComponent', () => { + let component: TasksComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TasksComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TasksComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/monitor/src/app/tasks/tasks.component.ts b/monitor/src/app/tasks/tasks.component.ts new file mode 100644 index 000000000..dac2d047d --- /dev/null +++ b/monitor/src/app/tasks/tasks.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit } from '@angular/core'; +import { RestConnectorService } from '../service/rest-connector.service'; +import { State } from '../model/state'; + +@Component({ + selector: 'tasks', + templateUrl: './tasks.component.html', + styleUrls: ['./tasks.component.css'], + providers: [RestConnectorService] +}) +export class TasksComponent implements OnInit { + + + pieChartLabels: string[] = ['Ready', 'Claimed', 'Completed']; + pieChartData: number[] = []; + pieChartType: string = 'pie'; + isDataAvailable: boolean = false; + + constructor(private restConnectorService: RestConnectorService) { } + + ngOnInit() { + this.restConnectorService.getTaskStatistics().subscribe(data => { + if (data.find(x => x.state == "READY") != null) { + this.pieChartData.push(data.find(x => x.state == "READY").counter); + } else { + this.pieChartData.push(0); + } + if (data.find(x => x.state == "CLAIMED") != null) { + this.pieChartData.push(data.find(x => x.state == "CLAIMED").counter); + } else { + this.pieChartData.push(0); + } + if (data.find(x => x.state == "COMPLETED") != null) { + this.pieChartData.push(data.find(x => x.state == "COMPLETED").counter); + } else { + this.pieChartData.push(0); + } + this.isDataAvailable = true; + }); + } +} diff --git a/monitor/src/app/workbasket/workbasket.component.css b/monitor/src/app/workbasket/workbasket.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/monitor/src/app/workbasket/workbasket.component.html b/monitor/src/app/workbasket/workbasket.component.html new file mode 100644 index 000000000..80e085e52 --- /dev/null +++ b/monitor/src/app/workbasket/workbasket.component.html @@ -0,0 +1,22 @@ +
+
+ + + + + + + + + +
Workbaskets{{label}}
{{d.label}}{{d && d.data[j]}}
+
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/monitor/src/app/workbasket/workbasket.component.spec.ts b/monitor/src/app/workbasket/workbasket.component.spec.ts new file mode 100644 index 000000000..e26f9a0e3 --- /dev/null +++ b/monitor/src/app/workbasket/workbasket.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkbasketComponent } from './workbasket.component'; + +describe('WorkbasketComponent', () => { + let component: WorkbasketComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WorkbasketComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkbasketComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/monitor/src/app/workbasket/workbasket.component.ts b/monitor/src/app/workbasket/workbasket.component.ts new file mode 100644 index 000000000..e2c03cdad --- /dev/null +++ b/monitor/src/app/workbasket/workbasket.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { RestConnectorService } from '../service/rest-connector.service'; +import { WorkbasketCounter } from '../model/workbasket-counter'; +import { WorkbasketCounterData } from '../model/workbasket-counter-data'; + +@Component({ + selector: 'workbasket', + templateUrl: './workbasket.component.html', + styleUrls: ['./workbasket.component.css'], + providers: [RestConnectorService] +}) +export class WorkbasketComponent implements OnInit { + + constructor(private restConnectorService: RestConnectorService) { } + + private counter: WorkbasketCounter; + isDataAvailable: boolean = false; + public lineChartLabels: Array; + public lineChartLegend: boolean = true; + public lineChartType: string = 'line'; + // lineChart + public lineChartData: Array; + + ngOnInit() { + this.restConnectorService.getWorkbasketStatistics().subscribe(data => { + console.log(data); + console.log(this.lineChartData); + this.lineChartLabels = data.dates; + this.lineChartData = data.data; + this.isDataAvailable = true; + console.log(this.lineChartData); + }); + } + + + + public lineChartOptions: any = { + responsive: true + }; + public lineChartColors: Array = [ + { // grey + backgroundColor: 'rgba(148,159,177,0.2)', + borderColor: 'rgba(148,159,177,1)', + pointBackgroundColor: 'rgba(148,159,177,1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(148,159,177,0.8)' + }, + { // dark grey + backgroundColor: 'rgba(77,83,96,0.2)', + borderColor: 'rgba(77,83,96,1)', + pointBackgroundColor: 'rgba(77,83,96,1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(77,83,96,1)' + }, + { // grey + backgroundColor: 'rgba(148,159,177,0.2)', + borderColor: 'rgba(148,159,177,1)', + pointBackgroundColor: 'rgba(148,159,177,1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(148,159,177,0.8)' + } + ]; + +} diff --git a/monitor/src/assets/.gitkeep b/monitor/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/monitor/src/environments/environment.prod.ts b/monitor/src/environments/environment.prod.ts new file mode 100644 index 000000000..42bc1f3d8 --- /dev/null +++ b/monitor/src/environments/environment.prod.ts @@ -0,0 +1,6 @@ +export const environment = { + production: true, + taskanaRestUrl: 'http://taskana-rest.mybluemix.net', + taskanaWorkplaceUrl: 'http://taskana-workplace.mybluemix.net', + taskanaAdminUrl: 'http://taskana-admin.mybluemix.net' +}; diff --git a/monitor/src/environments/environment.ts b/monitor/src/environments/environment.ts new file mode 100644 index 000000000..e4f027ffe --- /dev/null +++ b/monitor/src/environments/environment.ts @@ -0,0 +1,11 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false, + taskanaRestUrl: 'http://localhost:8080', + taskanaWorkplaceUrl: 'http://localhost:4200', + taskanaAdminUrl: 'http://localhost:4201' +}; diff --git a/monitor/src/favicon.ico b/monitor/src/favicon.ico new file mode 100644 index 000000000..8081c7cea Binary files /dev/null and b/monitor/src/favicon.ico differ diff --git a/monitor/src/index.html b/monitor/src/index.html new file mode 100644 index 000000000..0e22612dc --- /dev/null +++ b/monitor/src/index.html @@ -0,0 +1,19 @@ + + + + + + Monitor + + + + + + + + + Loading... + + + + \ No newline at end of file diff --git a/monitor/src/main.ts b/monitor/src/main.ts new file mode 100644 index 000000000..a9ca1caf8 --- /dev/null +++ b/monitor/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/monitor/src/polyfills.ts b/monitor/src/polyfills.ts new file mode 100644 index 000000000..bc94e7a6d --- /dev/null +++ b/monitor/src/polyfills.ts @@ -0,0 +1,72 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** 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'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/monitor/src/styles.css b/monitor/src/styles.css new file mode 100644 index 000000000..90d4ee007 --- /dev/null +++ b/monitor/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/monitor/src/test.ts b/monitor/src/test.ts new file mode 100644 index 000000000..9bf72267e --- /dev/null +++ b/monitor/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/monitor/src/tsconfig.app.json b/monitor/src/tsconfig.app.json new file mode 100644 index 000000000..5e2507db5 --- /dev/null +++ b/monitor/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/monitor/src/tsconfig.spec.json b/monitor/src/tsconfig.spec.json new file mode 100644 index 000000000..510e3f1fd --- /dev/null +++ b/monitor/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/monitor/src/typings.d.ts b/monitor/src/typings.d.ts new file mode 100644 index 000000000..ef5c7bd62 --- /dev/null +++ b/monitor/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/monitor/tsconfig.json b/monitor/tsconfig.json new file mode 100644 index 000000000..a35a8ee3a --- /dev/null +++ b/monitor/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2016", + "dom" + ] + } +} diff --git a/monitor/tslint.json b/monitor/tslint.json new file mode 100644 index 000000000..97adaa5e9 --- /dev/null +++ b/monitor/tslint.json @@ -0,0 +1,130 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs" + ], + "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, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +} diff --git a/rest/.gitignore b/rest/.gitignore new file mode 100644 index 000000000..e6dbc858f --- /dev/null +++ b/rest/.gitignore @@ -0,0 +1,25 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +*.orig + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/rest/.mvn/wrapper/maven-wrapper.jar b/rest/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..9cc84ea9b Binary files /dev/null and b/rest/.mvn/wrapper/maven-wrapper.jar differ diff --git a/rest/.mvn/wrapper/maven-wrapper.properties b/rest/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c31504370 --- /dev/null +++ b/rest/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/rest/mvnw b/rest/mvnw new file mode 100644 index 000000000..5bf251c07 --- /dev/null +++ b/rest/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/rest/mvnw.cmd b/rest/mvnw.cmd new file mode 100644 index 000000000..019bd74d7 --- /dev/null +++ b/rest/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/rest/pom.xml b/rest/pom.xml new file mode 100644 index 000000000..52bbbc9b1 --- /dev/null +++ b/rest/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.taskana + rest + 0.0.1-SNAPSHOT + jar + + rest + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.5.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + + org.taskana + taskana-core + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.h2database + h2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/rest/src/main/java/org/taskana/rest/CategoryController.java b/rest/src/main/java/org/taskana/rest/CategoryController.java new file mode 100644 index 000000000..7b1656a01 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/CategoryController.java @@ -0,0 +1,53 @@ +/** + * + */ +package org.taskana.rest; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.taskana.CategoryService; +import org.taskana.model.Category; + +@RestController +@RequestMapping(path = "/v1/categories", produces = { MediaType.APPLICATION_JSON_VALUE }) +public class CategoryController { + + @Autowired + private CategoryService categoryService; + + @RequestMapping + public List getCategories() { + return categoryService.selectCategories(); + } + + @RequestMapping(value = "/{categoryId}") + public Category getCategory(@PathVariable String categoryId) { + return categoryService.selectCategoryById(categoryId); + } + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity createCategory(@RequestBody Category category) { + try { + categoryService.insertCategory(category); + return ResponseEntity.status(HttpStatus.CREATED).body(category); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @RequestMapping(method = RequestMethod.PUT) + public ResponseEntity updateCategory(@RequestBody Category category) { + try { + categoryService.updateCategory(category); + return ResponseEntity.status(HttpStatus.CREATED).body(category); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + +} diff --git a/rest/src/main/java/org/taskana/rest/MonitorController.java b/rest/src/main/java/org/taskana/rest/MonitorController.java new file mode 100644 index 000000000..11935feec --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/MonitorController.java @@ -0,0 +1,100 @@ +package org.taskana.rest; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatterBuilder; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.taskana.TaskService; +import org.taskana.WorkbasketService; +import org.taskana.model.DueWorkbasketCounter; +import org.taskana.model.TaskState; +import org.taskana.model.TaskStateCounter; +import org.taskana.model.Workbasket; +import org.taskana.rest.model.WorkbasketCounterDataDto; +import org.taskana.rest.model.WorkbasketCounterDto; + +@RestController +@RequestMapping(path = "/v1/monitor", produces = { MediaType.APPLICATION_JSON_VALUE }) +public class MonitorController { + + @Autowired + private TaskService taskService; + + @Autowired + private WorkbasketService workbasketService; + + @RequestMapping(value = "/countByState") + public ResponseEntity> getTaskcountForState( + @RequestParam(value = "states") List taskStates) { + try { + List taskCount = taskService.getTaskCountForState(taskStates); + return ResponseEntity.status(HttpStatus.OK).body(taskCount); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @RequestMapping(value = "/taskcountByWorkbasketDaysAndState") + public ResponseEntity getTaskCountByWorkbasketAndDaysInPastAndState( + @RequestParam(value = "daysInPast") Long daysInPast, + @RequestParam(value = "states") List states) { + try { + WorkbasketCounterDto WorkbasketCounterDto = new WorkbasketCounterDto(); + + LocalDate date = LocalDate.now(); + date = date.minusDays(daysInPast); + List dates = new ArrayList<>(); + + for (int i = 0; i < (daysInPast * 2 + 1); i++) { + dates.add(date.format(new DateTimeFormatterBuilder().appendPattern("dd.MM.yyyy").toFormatter())); + date = date.plusDays(1); + } + WorkbasketCounterDto.setDates(dates); + + List data = new ArrayList<>(); + + for (Workbasket workbasket : workbasketService.getWorkbaskets()) { + WorkbasketCounterDataDto counterDto = new WorkbasketCounterDataDto(); + counterDto.setLabel(workbasket.getName()); + List zeroData = new ArrayList<>(); + for (int i = 0; i < dates.size(); i++) { + zeroData.add(0); + } + counterDto.setData(zeroData); + data.add(counterDto); + } + + List dwcList = taskService.getTaskCountByWorkbasketAndDaysInPastAndState(daysInPast, + states); + + for (DueWorkbasketCounter item : dwcList) { + String formattedDate = item.getDue().toLocalDate() + .format(new DateTimeFormatterBuilder().appendPattern("dd.MM.yyyy").toFormatter()); + for (int i = 0; i < dates.size(); i++) { + if (formattedDate.equalsIgnoreCase(dates.get(i))) { + for (int j = 0; j < data.size(); j++) { + if (data.get(j).getLabel().equalsIgnoreCase( + workbasketService.getWorkbasket(item.getWorkbasketId()).getName())) { + data.get(j).getData().set(i, (int) item.getTaskCounter()); + } + } + } + } + } + + WorkbasketCounterDto.setData(data); + + return ResponseEntity.status(HttpStatus.OK).body(WorkbasketCounterDto); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} diff --git a/rest/src/main/java/org/taskana/rest/RestApplication.java b/rest/src/main/java/org/taskana/rest/RestApplication.java new file mode 100644 index 000000000..30a689af3 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/RestApplication.java @@ -0,0 +1,99 @@ +package org.taskana.rest; + +import java.sql.SQLException; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.SpringHandlerInstantiator; +import org.taskana.CategoryService; +import org.taskana.TaskService; +import org.taskana.TaskanaEngine; +import org.taskana.WorkbasketService; +import org.taskana.configuration.TaskanaEngineConfiguration; +import org.taskana.model.Workbasket; +import org.taskana.rest.serialization.WorkbasketMixIn; +import org.taskana.sampledata.SampleDataGenerator; + +import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; + +@SpringBootApplication +public class RestApplication { + + private static final Logger logger = LoggerFactory.getLogger(RestApplication.class); + + public static void main(String[] args) { + SpringApplication.run(RestApplication.class, args); + } + + @Bean + public CategoryService getCategoryService() throws Exception { + return getTaskanaEngine().getCategoryService(); + } + + @Bean + public TaskService getTaskService() throws Exception { + return getTaskanaEngine().getTaskService(); + } + + @Bean + public WorkbasketService getWorkbasketService() throws Exception { + return getTaskanaEngine().getWorkbasketService(); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public TaskanaEngine getTaskanaEngine() throws SQLException { + return getTaskanaEngineConfiguration().buildTaskanaEngine(); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public TaskanaEngineConfiguration getTaskanaEngineConfiguration() throws SQLException { + TaskanaEngineConfiguration taskanaEngineConfiguration = new TaskanaEngineConfiguration(null, true); + return taskanaEngineConfiguration; + } + + @PostConstruct + public void createSampleData() { + try { + new SampleDataGenerator(getTaskanaEngineConfiguration().createDefaultDataSource()).generateSampleData(); + } catch (SQLException e) { + logger.error("Could not create sample data.", e); + } + } + + /** + * Needed to override JSON De-/Serializer in Jackson. + * + * @param handlerInstantiator + * @return + */ + @Bean + public Jackson2ObjectMapperBuilder jacksonBuilder(HandlerInstantiator handlerInstantiator) { + Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder(); + b.indentOutput(true).mixIn(Workbasket.class, WorkbasketMixIn.class); + b.handlerInstantiator(handlerInstantiator); + return b; + } + + /** + * Needed for injection into jackson deserilizer. + * + * @param context + * @return + */ + @Bean + public HandlerInstantiator handlerInstantiator(ApplicationContext context) { + return new SpringHandlerInstantiator(context.getAutowireCapableBeanFactory()); + } + +} diff --git a/rest/src/main/java/org/taskana/rest/TaskController.java b/rest/src/main/java/org/taskana/rest/TaskController.java new file mode 100644 index 000000000..48c7d8196 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/TaskController.java @@ -0,0 +1,107 @@ +package org.taskana.rest; + +import java.util.List; + +import javax.security.auth.login.LoginException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.taskana.TaskService; +import org.taskana.TaskanaEngine; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.TaskNotFoundException; +import org.taskana.model.Task; + +@RestController +@RequestMapping(path = "/v1/tasks", produces = { MediaType.APPLICATION_JSON_VALUE }) +public class TaskController { + + @Autowired + TaskanaEngine taskanaEngine; + @Autowired + private TaskService taskService; + + @RequestMapping + public ResponseEntity> getTasks(@RequestParam MultiValueMap params) + throws LoginException { + try { + + if (params.keySet().size() == 0) { + return ResponseEntity.status(HttpStatus.OK).body(taskService.getTasks()); + } + if (params.containsKey("workbasketid") && params.containsKey("state")) { + System.out.println("HOLGER1"); + + return ResponseEntity.status(HttpStatus.OK) + .body(taskService.getTasksForWorkbasket(params.get("workbasketid"), params.get("state"))); + } + return ResponseEntity.status(HttpStatus.OK) + .body(taskService.getTasksForWorkbasket(params.getFirst("workbasketid"))); + } catch (NotAuthorizedException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } + + @RequestMapping(value = "/{taskId}") + public ResponseEntity getTask(@PathVariable(value = "taskId") String taskId) { + try { + Task task = taskService.getTaskById(taskId); + return ResponseEntity.status(HttpStatus.OK).body(task); + } catch (TaskNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + @RequestMapping(method = RequestMethod.POST, value = "/{taskId}/claim") + public ResponseEntity claimTask(@PathVariable String taskId, @RequestBody String userName) { + // TODO verify user + try { + taskService.claim(taskId, userName); + Task updatedTask = taskService.getTaskById(taskId); + return ResponseEntity.status(HttpStatus.OK).body(updatedTask); + } catch (TaskNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + @RequestMapping(method = RequestMethod.POST, value = "/{taskId}/complete") + public ResponseEntity completeTask(@PathVariable String taskId) { + try { + taskService.complete(taskId); + Task updatedTask = taskService.getTaskById(taskId); + return ResponseEntity.status(HttpStatus.OK).body(updatedTask); + } catch (TaskNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity createTask(@RequestBody Task task) { + try { + Task createdTask = taskService.create(task); + return ResponseEntity.status(HttpStatus.CREATED).body(createdTask); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @RequestMapping(method = RequestMethod.POST, value = "/{taskId}/transfer/{workbasketId}") + public ResponseEntity transferTask(@PathVariable String taskId, @PathVariable String workbasketId) { + try { + Task updatedTask = taskService.transfer(taskId, workbasketId); + return ResponseEntity.status(HttpStatus.CREATED).body(updatedTask); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + +} diff --git a/rest/src/main/java/org/taskana/rest/WorkbasketController.java b/rest/src/main/java/org/taskana/rest/WorkbasketController.java new file mode 100644 index 000000000..01357daa9 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/WorkbasketController.java @@ -0,0 +1,82 @@ +package org.taskana.rest; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.taskana.WorkbasketService; +import org.taskana.exceptions.NotAuthorizedException; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.Workbasket; +import org.taskana.model.WorkbasketAccessItem; + +@RestController +@RequestMapping(path = "/v1/workbaskets", produces = { MediaType.APPLICATION_JSON_VALUE }) +public class WorkbasketController { + + @Autowired + private WorkbasketService workbasketService; + + @GetMapping + public List getWorkbaskets() { + return workbasketService.getWorkbaskets(); + } + + @RequestMapping(value = "/{workbasketid}") + public ResponseEntity getWorkbasketById(@PathVariable(value = "workbasketid") String workbasketId) { + try { + Workbasket workbasket = workbasketService.getWorkbasket(workbasketId); + return new ResponseEntity<>(workbasket, HttpStatus.OK); + } catch (WorkbasketNotFoundException e) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @RequestMapping(method = RequestMethod.POST) + public ResponseEntity createWorkbasket(@RequestBody Workbasket workbasket) { + Workbasket createdWorkbasket = workbasketService.createWorkbasket(workbasket); + return new ResponseEntity<>(createdWorkbasket, HttpStatus.CREATED); + } + + @RequestMapping(value = "/{workbasketid}", method = RequestMethod.PUT) + public ResponseEntity updateWorkbasket(@PathVariable(value = "workbasketid") String workbasketId, + @RequestBody Workbasket workbasket) { + try { + Workbasket updatedWorkbasket = workbasketService.updateWorkbasket(workbasket); + return new ResponseEntity<>(updatedWorkbasket, HttpStatus.OK); + } catch (NotAuthorizedException e) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + } + + @RequestMapping(value = "/{workbasketid}/authorizations", method = RequestMethod.GET) + public List getWorkbasketAuthorizations( + @PathVariable(value = "workbasketid") String workbasketId) { + return workbasketService.getWorkbasketAuthorizations(workbasketId); + } + + @RequestMapping(value = "/authorizations", method = RequestMethod.POST) + public WorkbasketAccessItem createWorkbasketAuthorization(@RequestBody WorkbasketAccessItem workbasketAccessItem) { + return workbasketService.createWorkbasketAuthorization(workbasketAccessItem); + } + + @RequestMapping(value = "/authorizations/{authid}", method = RequestMethod.PUT) + public WorkbasketAccessItem updateWorkbasketAuthorization(@PathVariable(value = "authid") String authId, + @RequestBody WorkbasketAccessItem workbasketAccessItem) { + return workbasketService.updateWorkbasketAuthorization(workbasketAccessItem); + } + + @RequestMapping(value = "/authorizations/{authid}", method = RequestMethod.DELETE) + public ResponseEntity deleteWorkbasketAuthorization(@PathVariable(value = "authid") String authId) { + workbasketService.deleteWorkbasketAuthorization(authId); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } +} diff --git a/rest/src/main/java/org/taskana/rest/model/WorkbasketCounterDataDto.java b/rest/src/main/java/org/taskana/rest/model/WorkbasketCounterDataDto.java new file mode 100644 index 000000000..1859fdd74 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/model/WorkbasketCounterDataDto.java @@ -0,0 +1,20 @@ +package org.taskana.rest.model; + +import java.util.List; + +public class WorkbasketCounterDataDto { + private List data; + private String label; + public List getData() { + return data; + } + public void setData(List data) { + this.data = data; + } + public String getLabel() { + return label; + } + public void setLabel(String label) { + this.label = label; + } +} diff --git a/rest/src/main/java/org/taskana/rest/model/WorkbasketCounterDto.java b/rest/src/main/java/org/taskana/rest/model/WorkbasketCounterDto.java new file mode 100644 index 000000000..03d6acec6 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/model/WorkbasketCounterDto.java @@ -0,0 +1,25 @@ +package org.taskana.rest.model; + +import java.util.List; + +public class WorkbasketCounterDto { + + private List dates; + private List data; + + public List getDates() { + return dates; + } + + public void setDates(List dates) { + this.dates = dates; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } +} diff --git a/rest/src/main/java/org/taskana/rest/security/CustomAutenticationProvider.java b/rest/src/main/java/org/taskana/rest/security/CustomAutenticationProvider.java new file mode 100644 index 000000000..3fd8323ab --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/security/CustomAutenticationProvider.java @@ -0,0 +1,31 @@ +package org.taskana.rest.security; + +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.jaas.JaasAuthenticationToken; +import org.springframework.security.core.Authentication; + +public class CustomAutenticationProvider implements AuthenticationProvider { + private AuthenticationProvider delegate; + + public CustomAutenticationProvider(AuthenticationProvider delegate) { + this.delegate = delegate; + } + + @Override + public Authentication authenticate(Authentication authentication) { + JaasAuthenticationToken jaasAuthenticationToken = (JaasAuthenticationToken) delegate + .authenticate(authentication); + + if (jaasAuthenticationToken.isAuthenticated()) { + jaasAuthenticationToken.getLoginContext().getSubject().getPublicCredentials().add(jaasAuthenticationToken.getPrincipal()); + return jaasAuthenticationToken; + } else { + return null; + } + } + + @Override + public boolean supports(Class authentication) { + return delegate.supports(authentication); + } +} diff --git a/rest/src/main/java/org/taskana/rest/security/RoleGranterFromMap.java b/rest/src/main/java/org/taskana/rest/security/RoleGranterFromMap.java new file mode 100644 index 000000000..d74da875a --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/security/RoleGranterFromMap.java @@ -0,0 +1,23 @@ +package org.taskana.rest.security; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.authentication.jaas.AuthorityGranter; + +public class RoleGranterFromMap implements AuthorityGranter { + + private static Map USER_ROLES = new HashMap(); + + static { + USER_ROLES.put("test", "ROLE_ADMINISTRATOR"); + // USER_ROLES.put("test", "TRUE"); + } + + public Set grant(Principal principal) { + return Collections.singleton("DUMMY"); + } +} diff --git a/rest/src/main/java/org/taskana/rest/security/SampleLoginModule.java b/rest/src/main/java/org/taskana/rest/security/SampleLoginModule.java new file mode 100644 index 000000000..3401381c7 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/security/SampleLoginModule.java @@ -0,0 +1,52 @@ +package org.taskana.rest.security; + +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +public class SampleLoginModule implements LoginModule { + + private Subject subject; + private String password; + private String username; + + public boolean abort() throws LoginException { + return true; + } + + public boolean commit() throws LoginException { + return true; + } + + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, + Map options) { + this.subject = subject; + + try { + NameCallback nameCallback = new NameCallback("prompt"); + PasswordCallback passwordCallback = new PasswordCallback("prompt", false); + + callbackHandler.handle(new Callback[] { nameCallback, passwordCallback }); + + this.password = new String(passwordCallback.getPassword()); + this.username = nameCallback.getName(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public boolean login() throws LoginException { + return true; + } + + public boolean logout() throws LoginException { + return true; + } + +} diff --git a/rest/src/main/java/org/taskana/rest/security/WebSecurityConfig.java b/rest/src/main/java/org/taskana/rest/security/WebSecurityConfig.java new file mode 100644 index 000000000..1ab79c971 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/security/WebSecurityConfig.java @@ -0,0 +1,81 @@ +package org.taskana.rest.security; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.jaas.AuthorityGranter; +import org.springframework.security.authentication.jaas.JaasAuthenticationCallbackHandler; +import org.springframework.security.authentication.jaas.JaasAuthenticationProvider; +import org.springframework.security.authentication.jaas.JaasNameCallbackHandler; +import org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("Max").password("test").roles("ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable().authenticationProvider(customauthProvider()).authorizeRequests() + .antMatchers(HttpMethod.GET, "/**").authenticated().and().httpBasic().and() + .addFilter(new JaasApiIntegrationFilter()); + } + + @Bean + public AuthenticationProvider customauthProvider() { + return new CustomAutenticationProvider(jaasAuthProvider()); + } + + @Bean + public JaasAuthenticationProvider jaasAuthProvider() { + JaasAuthenticationProvider authenticationProvider = new JaasAuthenticationProvider(); + authenticationProvider.setAuthorityGranters(new AuthorityGranter[] { new RoleGranterFromMap() }); + authenticationProvider.setCallbackHandlers(new JaasAuthenticationCallbackHandler[] { + new JaasNameCallbackHandler(), new JaasPasswordCallbackHandler() }); + authenticationProvider.setLoginContextName("taskana"); + authenticationProvider.setLoginConfig(new ClassPathResource("pss_jaas.config")); + return authenticationProvider; + } + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurerAdapter() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("*"); + } + }; + } + + @Bean + public FilterRegistrationBean corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", config); + FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); + bean.setOrder(0); + return bean; + } +} diff --git a/rest/src/main/java/org/taskana/rest/serialization/DistributionTargetDeserializer.java b/rest/src/main/java/org/taskana/rest/serialization/DistributionTargetDeserializer.java new file mode 100644 index 000000000..c093e43ed --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/serialization/DistributionTargetDeserializer.java @@ -0,0 +1,56 @@ +package org.taskana.rest.serialization; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import org.taskana.WorkbasketService; +import org.taskana.exceptions.WorkbasketNotFoundException; +import org.taskana.model.Workbasket; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +/** + * This class deserializes the string list to real workbaskets + */ +public class DistributionTargetDeserializer extends StdDeserializer> { + + private static final long serialVersionUID = 4226950057149602129L; + + private static final Logger logger = LoggerFactory.getLogger(DistributionTargetDeserializer.class); + + @Autowired + private WorkbasketService workbasketService; + + public DistributionTargetDeserializer() { + this(null); + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + } + + public DistributionTargetDeserializer(Class vc) { + super(vc); + } + + @Override + public List deserialize(JsonParser jsonparser, DeserializationContext context) + throws IOException, JsonProcessingException { + List distributionTargets = new ArrayList(); + while (jsonparser.nextToken() != JsonToken.END_ARRAY) { + String id = jsonparser.getText(); + try { + distributionTargets.add(workbasketService.getWorkbasket(id)); + } catch (WorkbasketNotFoundException e) { + logger.error("The workbasket with the id '" + id + "' is not found in database."); + } + } + return distributionTargets; + } +} diff --git a/rest/src/main/java/org/taskana/rest/serialization/DistributionTargetSerializer.java b/rest/src/main/java/org/taskana/rest/serialization/DistributionTargetSerializer.java new file mode 100644 index 000000000..5bea3ad08 --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/serialization/DistributionTargetSerializer.java @@ -0,0 +1,38 @@ +package org.taskana.rest.serialization; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.taskana.model.Workbasket; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +/** + * This class serializes the distribution targets to an string array with ids + */ +public class DistributionTargetSerializer extends StdSerializer> { + + private static final long serialVersionUID = -4655804943794734821L; + + public DistributionTargetSerializer() { + this(null); + } + + public DistributionTargetSerializer(Class> t) { + super(t); + } + + @Override + public void serialize(List workbaskets, JsonGenerator gen, SerializerProvider provider) + throws IOException { + List ids = new ArrayList<>(); + + for (Workbasket item : workbaskets) { + ids.add(item.getId()); + } + gen.writeObject(ids); + } +} diff --git a/rest/src/main/java/org/taskana/rest/serialization/WorkbasketMixIn.java b/rest/src/main/java/org/taskana/rest/serialization/WorkbasketMixIn.java new file mode 100644 index 000000000..dad1b7daf --- /dev/null +++ b/rest/src/main/java/org/taskana/rest/serialization/WorkbasketMixIn.java @@ -0,0 +1,20 @@ +package org.taskana.rest.serialization; + +import java.util.List; + +import org.taskana.model.Workbasket; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +/** + * This class is used to override the distributiontargets with non standard + * serialization classes + */ +public abstract class WorkbasketMixIn { + + @JsonSerialize(using = DistributionTargetSerializer.class) + @JsonDeserialize(using = DistributionTargetDeserializer.class) + abstract List getDistributionTargets(); + +} diff --git a/rest/src/main/java/org/taskana/sampledata/SampleDataGenerator.java b/rest/src/main/java/org/taskana/sampledata/SampleDataGenerator.java new file mode 100644 index 000000000..ed2b49136 --- /dev/null +++ b/rest/src/main/java/org/taskana/sampledata/SampleDataGenerator.java @@ -0,0 +1,66 @@ +package org.taskana.sampledata; + +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.apache.ibatis.jdbc.ScriptRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.taskana.rest.RestApplication; + +public class SampleDataGenerator { + + private static final Logger logger = LoggerFactory.getLogger(SampleDataGenerator.class); + private ScriptRunner runner; + + private static final String SQL = "/sql"; + private static final String TEST_DATA = "/sample-data"; + private static final String TASK = SQL + TEST_DATA + "/task.sql"; + private static final String WORKBASKET = SQL + TEST_DATA + "/workbasket.sql"; + private static final String DISTRIBUTION_TARGETS = SQL + TEST_DATA + "/distribution-targets.sql"; + private static final String WORKBASKET_ACCESS_LIST = SQL + TEST_DATA + "/workbasket-access-list.sql"; + private static final String CATEGORY = SQL + TEST_DATA + "/category.sql"; + + + public SampleDataGenerator(DataSource dataSource) throws SQLException { + if (logger.isDebugEnabled()) { + logger.debug(dataSource.getConnection().getMetaData().toString()); + } + runner = new ScriptRunner(dataSource.getConnection()); + } + + /** + * Creates sample data for manual testing purposes. + * + * @throws SQLException + */ + public void generateSampleData() throws SQLException { + StringWriter outWriter = new StringWriter(); + PrintWriter logWriter = new PrintWriter(outWriter); + + StringWriter errorWriter = new StringWriter(); + PrintWriter errorLogWriter = new PrintWriter(errorWriter); + + runner.setStopOnError(true); + runner.setLogWriter(logWriter); + runner.setErrorLogWriter(errorLogWriter); + + runner.runScript(new InputStreamReader(this.getClass().getResourceAsStream(TASK))); + runner.runScript(new InputStreamReader(this.getClass().getResourceAsStream(WORKBASKET))); + runner.runScript(new InputStreamReader(this.getClass().getResourceAsStream(DISTRIBUTION_TARGETS))); + runner.runScript(new InputStreamReader(this.getClass().getResourceAsStream(CATEGORY))); + runner.runScript(new InputStreamReader(this.getClass().getResourceAsStream(WORKBASKET_ACCESS_LIST))); + + runner.closeConnection(); + + logger.debug(outWriter.toString()); + if (!errorWriter.toString().trim().isEmpty()) { + logger.error(errorWriter.toString()); + } + } + +} diff --git a/rest/src/main/resources/application.properties b/rest/src/main/resources/application.properties new file mode 100644 index 000000000..e69de29bb diff --git a/rest/src/main/resources/pss_jaas.config b/rest/src/main/resources/pss_jaas.config new file mode 100644 index 000000000..40f3bcfec --- /dev/null +++ b/rest/src/main/resources/pss_jaas.config @@ -0,0 +1,3 @@ +taskana { + org.taskana.rest.security.SampleLoginModule required; +}; diff --git a/rest/src/main/resources/sql/sample-data/category.sql b/rest/src/main/resources/sql/sample-data/category.sql new file mode 100644 index 000000000..7a5805b20 --- /dev/null +++ b/rest/src/main/resources/sql/sample-data/category.sql @@ -0,0 +1,4 @@ +INSERT INTO BUSINESS_CATEGORY (ID, TENANT_ID, PARENT_CATEGORY_ID, CREATED, NAME, DESCRIPTION, PRIORITY, SERVICE_LEVEL) VALUES ('1', 'NT', '', CURRENT_TIMESTAMP, 'ROOT', 'DESC', 1, 'SLA'); +INSERT INTO BUSINESS_CATEGORY (ID, TENANT_ID, PARENT_CATEGORY_ID, CREATED, NAME, DESCRIPTION, PRIORITY, SERVICE_LEVEL) VALUES ('2', 'NT', '1', CURRENT_TIMESTAMP, 'CHILD', 'DESC', 1, 'SLA'); +INSERT INTO BUSINESS_CATEGORY (ID, TENANT_ID, PARENT_CATEGORY_ID, CREATED, NAME, DESCRIPTION, PRIORITY, SERVICE_LEVEL) VALUES ('3', 'NT', '1', CURRENT_TIMESTAMP, 'ANOTHER CHILD', 'DESC', 1, 'SLA'); +INSERT INTO BUSINESS_CATEGORY (ID, TENANT_ID, PARENT_CATEGORY_ID, CREATED, NAME, DESCRIPTION, PRIORITY, SERVICE_LEVEL) VALUES ('4', 'NT', '2', CURRENT_TIMESTAMP, 'GRANDCHILD', 'DESC', 1, 'SLA'); \ No newline at end of file diff --git a/rest/src/main/resources/sql/sample-data/distribution-targets.sql b/rest/src/main/resources/sql/sample-data/distribution-targets.sql new file mode 100644 index 000000000..70c64997e --- /dev/null +++ b/rest/src/main/resources/sql/sample-data/distribution-targets.sql @@ -0,0 +1 @@ +INSERT INTO DISTRIBUTION_TARGETS VALUES ('1','2'); \ No newline at end of file diff --git a/rest/src/main/resources/sql/sample-data/task.sql b/rest/src/main/resources/sql/sample-data/task.sql new file mode 100644 index 000000000..8aff0fec5 --- /dev/null +++ b/rest/src/main/resources/sql/sample-data/task.sql @@ -0,0 +1,17 @@ + +INSERT INTO TASK VALUES('1', '1', CURRENT_TIMESTAMP, null, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task1', 'Lorem ipsum dolor sit amet.', 1, 'READY', 'Task', '1', 'Stefan'); +INSERT INTO TASK VALUES('2', '1', CURRENT_TIMESTAMP, null, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task2', 'Lorem ipsum dolor sit amet. ', 1, 'READY', 'Task', '1', 'Frank'); +INSERT INTO TASK VALUES('3', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task3', 'Lorem ipsum dolor sit amet. ', 1, 'CLAIMED', 'Task', '1', 'Stefan'); +INSERT INTO TASK VALUES('4', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task4', 'Lorem ipsum dolor sit amet.', 1, 'CLAIMED', 'Task', '1', 'Frank'); +INSERT INTO TASK VALUES('5', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task5', 'Lorem ipsum dolor sit amet. ', 1, 'COMPLETED', 'Task', '1', 'Stefan'); +INSERT INTO TASK VALUES('6', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task6', 'Lorem ipsum dolor sit amet.', 1, 'COMPLETED', 'Task', '1', 'Frank'); + +INSERT INTO TASK VALUES('7', '1', CURRENT_TIMESTAMP, null, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task7', 'Lorem ipsum dolor sit amet.', 1, 'READY', 'Task', '2', 'Stefan'); +INSERT INTO TASK VALUES('8', '1', CURRENT_TIMESTAMP, null, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task8', 'Lorem ipsum dolor sit amet. ', 1, 'READY', 'Task', '2', 'Frank'); +INSERT INTO TASK VALUES('9', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task9', 'Lorem ipsum dolor sit amet. ', 1, 'CLAIMED', 'Task', '2', 'Stefan'); +INSERT INTO TASK VALUES('10', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task10', 'Lorem ipsum dolor sit amet.', 1, 'CLAIMED', 'Task', '2', 'Frank'); +INSERT INTO TASK VALUES('11', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task11', 'Lorem ipsum dolor sit amet. ', 1, 'COMPLETED', 'Task', '2', 'Stefan'); +INSERT INTO TASK VALUES('12', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task12', 'Lorem ipsum dolor sit amet.', 1, 'COMPLETED', 'Task', '2', 'Frank'); + +INSERT INTO TASK VALUES('13', '1', CURRENT_TIMESTAMP, null, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task12', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus massa turpis, pellentesque ut libero sit amet, malesuada suscipit dolor. Sed volutpat euismod felis sit amet molestie. Fusce ornare purus dui. ', 1, 'READY', 'Task', '2', 'Frank'); +INSERT INTO TASK VALUES('14', '1', CURRENT_TIMESTAMP, null, null, CURRENT_TIMESTAMP, null, CURRENT_TIMESTAMP, 'Task6', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis vulputate nibh ut malesuada. Etiam ac dictum tellus, nec cursus nunc. Curabitur velit eros, feugiat volutpat laoreet vitae, cursus eu dui. Nulla ut purus sem. Vivamus aliquet odio vitae erat cursus, vitae mattis urna mollis. Nam quam tellus, auctor id volutpat congue, viverra vitae ante. Duis nisi dolor, elementum et mattis at, maximus id velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis eu condimentum risus. Praesent libero velit, facilisis sit amet maximus non, scelerisque ullamcorper leo. Ut sit amet iaculis eros. Mauris sagittis nibh lacus, at facilisis magna suscipit at. Aliquam finibus tempor odio id commodo. Vivamus aliquam, justo id porta imperdiet, mi.', 1, 'READY', 'Task', '1', 'Frank'); \ No newline at end of file diff --git a/rest/src/main/resources/sql/sample-data/workbasket-access-list.sql b/rest/src/main/resources/sql/sample-data/workbasket-access-list.sql new file mode 100644 index 000000000..900ddbd3f --- /dev/null +++ b/rest/src/main/resources/sql/sample-data/workbasket-access-list.sql @@ -0,0 +1,3 @@ +INSERT INTO WORKBASKET_ACCESS_LIST VALUES ('1', '1', 'Elena', null, true, true, true, true, true); +INSERT INTO WORKBASKET_ACCESS_LIST VALUES ('2', '2', 'Max', null, true, true, true, true, true); +INSERT INTO WORKBASKET_ACCESS_LIST VALUES ('3', '3', 'Simone', null, true, true, true, true, true); diff --git a/rest/src/main/resources/sql/sample-data/workbasket.sql b/rest/src/main/resources/sql/sample-data/workbasket.sql new file mode 100644 index 000000000..eaa874245 --- /dev/null +++ b/rest/src/main/resources/sql/sample-data/workbasket.sql @@ -0,0 +1,3 @@ +INSERT INTO WORKBASKET VALUES ('1', '1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Basket1', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Peter'); +INSERT INTO WORKBASKET VALUES ('2', '2', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Basket2', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Susanne'); +INSERT INTO WORKBASKET VALUES ('3', '2', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Basket3', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Max'); diff --git a/workplace/.angular-cli.json b/workplace/.angular-cli.json new file mode 100644 index 000000000..6e7954d24 --- /dev/null +++ b/workplace/.angular-cli.json @@ -0,0 +1,58 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "workplace" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "../node_modules/bootstrap/dist/css/bootstrap.min.css", + "styles.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/workplace/.editorconfig b/workplace/.editorconfig new file mode 100644 index 000000000..6e87a003d --- /dev/null +++ b/workplace/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/workplace/.gitignore b/workplace/.gitignore new file mode 100644 index 000000000..54bfd2001 --- /dev/null +++ b/workplace/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db diff --git a/workplace/LICENSE b/workplace/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/workplace/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/workplace/README.md b/workplace/README.md new file mode 100644 index 000000000..f570f2947 --- /dev/null +++ b/workplace/README.md @@ -0,0 +1,7 @@ +[![Build Status](https://travis-ci.org/Taskana/hackathon.svg?branch=master)](https://travis-ci.org/Taskana/hackathon) +[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) + +# Hackathon + +## Link to UI +http://taskana-workplace.mybluemix.net diff --git a/workplace/e2e/app.e2e-spec.ts b/workplace/e2e/app.e2e-spec.ts new file mode 100644 index 000000000..3eb3b8001 --- /dev/null +++ b/workplace/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { WorkplacePage } from './app.po'; + +describe('workplace App', () => { + let page: WorkplacePage; + + beforeEach(() => { + page = new WorkplacePage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/workplace/e2e/app.po.ts b/workplace/e2e/app.po.ts new file mode 100644 index 000000000..2ade93aae --- /dev/null +++ b/workplace/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class WorkplacePage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/workplace/e2e/tsconfig.e2e.json b/workplace/e2e/tsconfig.e2e.json new file mode 100644 index 000000000..e2a9a2fc7 --- /dev/null +++ b/workplace/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/workplace/karma.conf.js b/workplace/karma.conf.js new file mode 100644 index 000000000..84b4cd5ac --- /dev/null +++ b/workplace/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/workplace/manifest.yml b/workplace/manifest.yml new file mode 100644 index 000000000..4183b6b94 --- /dev/null +++ b/workplace/manifest.yml @@ -0,0 +1,24 @@ +# Configuration file for Cloud Foundry, see https://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html +applications: +- name: taskana-rest + path: rest/target/rest-0.0.1-SNAPSHOT.jar + buildpack: https://github.com/cloudfoundry/java-buildpack.git#v3.10 + memory: 512M + disk_quota: 256M +- name: taskana-workplace + path: workplace/dist + buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git#v1.3.16 + memory: 16M + disk_quota: 64M +- name: taskana-admin + path: admin/dist + buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git#v1.3.16 + memory: 16M + disk_quota: 64M +- name: taskana-monitor + path: monitor/dist + buildpack: https://github.com/cloudfoundry/staticfile-buildpack.git#v1.3.16 + memory: 16M + disk_quota: 64M + + diff --git a/workplace/package.json b/workplace/package.json new file mode 100644 index 000000000..75cec641a --- /dev/null +++ b/workplace/package.json @@ -0,0 +1,49 @@ +{ + "name": "workplace", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/common": "^4.0.0", + "@angular/compiler": "^4.0.0", + "@angular/core": "^4.0.0", + "@angular/forms": "^4.0.0", + "@angular/http": "^4.0.0", + "@angular/platform-browser": "^4.0.0", + "@angular/platform-browser-dynamic": "^4.0.0", + "@angular/router": "^4.0.0", + "bootstrap": "^3.3.7", + "core-js": "^2.4.1", + "ng2-auto-complete": "^0.12.0", + "ngx-bootstrap": "^1.6.6", + "rxjs": "^5.1.0", + "zone.js": "^0.8.4" + }, + "devDependencies": { + "@angular/cli": "1.0.3", + "@angular/compiler-cli": "^4.0.0", + "@types/jasmine": "2.5.38", + "@types/node": "~6.0.60", + "codelyzer": "~2.0.0", + "jasmine-core": "~2.5.2", + "jasmine-spec-reporter": "~3.2.0", + "karma": "~1.4.1", + "karma-chrome-launcher": "~2.1.1", + "karma-cli": "~1.0.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-coverage-istanbul-reporter": "^0.2.0", + "protractor": "~5.1.0", + "ts-node": "~2.0.0", + "tslint": "~4.5.0", + "typescript": "~2.2.0" + } +} diff --git a/workplace/protractor.conf.js b/workplace/protractor.conf.js new file mode 100644 index 000000000..1c5e1e5a4 --- /dev/null +++ b/workplace/protractor.conf.js @@ -0,0 +1,30 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/workplace/src/app/app-routing.module.ts b/workplace/src/app/app-routing.module.ts new file mode 100644 index 000000000..8cdcbaf1b --- /dev/null +++ b/workplace/src/app/app-routing.module.ts @@ -0,0 +1,32 @@ + import { NgModule } from '@angular/core'; + import { RouterModule, Routes } from '@angular/router'; + import { AppComponent } from './app.component'; + import { TaskComponent } from './task/task.component'; + import { TasksComponent } from './tasks/tasks.component'; + + const appRoutes: Routes = [ + { + path: 'tasks', + component: TasksComponent + }, + { + path: 'tasks/:id', + component: TaskComponent + }, + { + path: '', + redirectTo: 'tasks', + pathMatch: 'full' + }, + ]; + @NgModule({ + imports: [ + RouterModule.forRoot( + appRoutes + ) + ], + exports: [ + RouterModule + ] + }) + export class AppRoutingModule {} \ No newline at end of file diff --git a/workplace/src/app/app.component.css b/workplace/src/app/app.component.css new file mode 100644 index 000000000..aa8b00fea --- /dev/null +++ b/workplace/src/app/app.component.css @@ -0,0 +1,3 @@ +.clickable { + cursor: pointer; +} \ No newline at end of file diff --git a/workplace/src/app/app.component.html b/workplace/src/app/app.component.html new file mode 100644 index 000000000..90c6b6463 --- /dev/null +++ b/workplace/src/app/app.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/workplace/src/app/app.component.spec.ts b/workplace/src/app/app.component.spec.ts new file mode 100644 index 000000000..c740bcd74 --- /dev/null +++ b/workplace/src/app/app.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + 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 works!'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app works!'); + })); + + 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('app works!'); + })); +}); diff --git a/workplace/src/app/app.component.ts b/workplace/src/app/app.component.ts new file mode 100644 index 000000000..1dbc8b54a --- /dev/null +++ b/workplace/src/app/app.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; +import { DataService } from './services/data.service'; +import { RestConnectorService } from './services/rest-connector.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], + providers: [ DataService, RestConnectorService ] +}) +export class AppComponent { +} diff --git a/workplace/src/app/app.module.ts b/workplace/src/app/app.module.ts new file mode 100644 index 000000000..a699d5b04 --- /dev/null +++ b/workplace/src/app/app.module.ts @@ -0,0 +1,41 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; +import { Ng2AutoCompleteModule } from 'ng2-auto-complete'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; +import { AlertModule } from 'ngx-bootstrap'; +import { SelectorComponent } from './workbasket-selector/workbasket-selector.component'; +import { TasklistComponent } from './tasklist/tasklist.component'; +import { TaskdetailsComponent } from './taskdetails/taskdetails.component'; + +import { OrderTasksByPipe } from './util/orderTasksBy.pipe'; + +import { TaskComponent } from './task/task.component'; +import { TasksComponent } from './tasks/tasks.component'; + + +@NgModule({ + declarations: [ + AppComponent, + SelectorComponent, + TasklistComponent, + TaskdetailsComponent, + OrderTasksByPipe, + TaskComponent, + TasksComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule, + AlertModule.forRoot(), + Ng2AutoCompleteModule, + AppRoutingModule, + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/workplace/src/app/model/task.ts b/workplace/src/app/model/task.ts new file mode 100644 index 000000000..cb5f1a3f1 --- /dev/null +++ b/workplace/src/app/model/task.ts @@ -0,0 +1,40 @@ +export class Task { + + id: string; + tenantId: string; + created: any; + claimed: any; + completed: any; + modified: any; + planned: any; + due: any; + name: string; + description: string; + priority: number; + state: string; + type: string; + workbasket: string; + owner: string; + + static create(data){ + return new Task(data); + } + + constructor(data){ + this.id = data.id; + this.tenantId = data.tenantId; + this.created = data.created; + this.claimed = data.claimed; + this.completed = data.completed; + this.modified = data.modified; + this.planned = data.planned; + this.due = data.due; + this.name = data.name; + this.description = data.description; + this.priority = data.priority; + this.state = data.state; + this.type = data.type; + this.workbasket = data.workbasket; + this.owner = data.owner; + } +} \ No newline at end of file diff --git a/workplace/src/app/model/workbasket.ts b/workplace/src/app/model/workbasket.ts new file mode 100644 index 000000000..6c0b2ae71 --- /dev/null +++ b/workplace/src/app/model/workbasket.ts @@ -0,0 +1,9 @@ +export class Workbasket { + id: string; + tenant_id: string; + created: string; + modified: string; + name: string; + description: string; + owner: string; +} \ No newline at end of file diff --git a/workplace/src/app/services/data.service.ts b/workplace/src/app/services/data.service.ts new file mode 100644 index 000000000..9dbc73608 --- /dev/null +++ b/workplace/src/app/services/data.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class DataService { + + workbasketId: string; + workbasketName: string; +} \ No newline at end of file diff --git a/workplace/src/app/services/rest-connector.service.spec.ts b/workplace/src/app/services/rest-connector.service.spec.ts new file mode 100644 index 000000000..e8e748bbf --- /dev/null +++ b/workplace/src/app/services/rest-connector.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { RestConnectorService } from './rest-connector.service'; + +describe('RestConnectorService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RestConnectorService] + }); + }); + + it('should be created', inject([RestConnectorService], (service: RestConnectorService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/workplace/src/app/services/rest-connector.service.ts b/workplace/src/app/services/rest-connector.service.ts new file mode 100644 index 000000000..f84599d8b --- /dev/null +++ b/workplace/src/app/services/rest-connector.service.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@angular/core'; +import { Headers, RequestOptions, Http, Response } from '@angular/http'; + +import { Workbasket } from '../model/workbasket'; +import { Task } from '../model/task'; +import { environment } from '../../environments/environment'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class RestConnectorService { + + constructor(private http: Http) { } + + getAllWorkBaskets(): Observable { + return this.http.get(environment.taskanaRestUrl + "/v1/workbaskets", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + findTaskWithWorkbaskets(basketName: string): Observable { + + return this.http.get(environment.taskanaRestUrl + "/v1/tasks?workbasketid=" + basketName + "&state=READY&state=CLAIMED", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + getTask(id: string): Observable { + return this.http.get(environment.taskanaRestUrl + "/v1/tasks/" + id, this.createAuthorizationHeader()) + .map(res => res.json()); + } + + completeTask(id: string): Observable { + return this.http.post(environment.taskanaRestUrl + "/v1/tasks/" + id + "/complete", "", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + claimTask(id: string): Observable { + return this.http.post(environment.taskanaRestUrl + "/v1/tasks/" + id + "/claim", "test", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + transferTask(taskId: string, workbasketId: string) { + return this.http.post(environment.taskanaRestUrl + "/v1/tasks/" + taskId + "/transfer/" + workbasketId, "", this.createAuthorizationHeader()) + .map(res => res.json()); + } + + private createAuthorizationHeader(){ + let headers: Headers = new Headers(); + headers.append("Authorization", "Basic TWF4OnRlc3Q="); + + return new RequestOptions({ headers: headers }); + } +} diff --git a/workplace/src/app/task/task.component.css b/workplace/src/app/task/task.component.css new file mode 100644 index 000000000..3580d0b57 --- /dev/null +++ b/workplace/src/app/task/task.component.css @@ -0,0 +1,9 @@ + +iframe { + position: absolute; + margin: 0; + padding: 0; + height: 600px; + width: 98%; + overflow: auto; +} \ No newline at end of file diff --git a/workplace/src/app/task/task.component.html b/workplace/src/app/task/task.component.html new file mode 100644 index 000000000..6af3ec3d4 --- /dev/null +++ b/workplace/src/app/task/task.component.html @@ -0,0 +1,19 @@ + + +
+ +
diff --git a/workplace/src/app/task/task.component.spec.ts b/workplace/src/app/task/task.component.spec.ts new file mode 100644 index 000000000..992af5761 --- /dev/null +++ b/workplace/src/app/task/task.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TaskComponent } from './task.component'; + +describe('TaskComponent', () => { + let component: TaskComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TaskComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TaskComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/workplace/src/app/task/task.component.ts b/workplace/src/app/task/task.component.ts new file mode 100644 index 000000000..5800088a8 --- /dev/null +++ b/workplace/src/app/task/task.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { Task } from '../model/task'; +import { RestConnectorService } from '../services/rest-connector.service'; +import { Workbasket } from '../model/workbasket'; +import { SafeResourceUrl, DomSanitizer} from '@angular/platform-browser'; + + +@Component({ + selector: 'app-task', + templateUrl: './task.component.html', + styleUrls: ['./task.component.css'] +}) +export class TaskComponent implements OnInit { + + task: Task = null; + link: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl("https://duckduckgo.com/?q="); + autoCompleteData: string[] = new Array; + workbasket: string = null; + workbasketId: string; + workbaskets: Workbasket[]; + + private sub: any; + + constructor(private restConnectorService:RestConnectorService, private route:ActivatedRoute, private router:Router, private sanitizer: DomSanitizer) { } + + ngOnInit() { + let id = this.route.snapshot.params['id']; + this.restConnectorService.getTask(id).subscribe( + t => { + this.task = t; + this.link = this.sanitizer.bypassSecurityTrustResourceUrl("https://duckduckgo.com/?q=" + this.task.name ); + this.restConnectorService.getAllWorkBaskets().subscribe( w => { + this.workbaskets = w; + this.workbaskets.forEach(workbasket => { + if(workbasket.id != this.task.workbasket) { + this.autoCompleteData.push(workbasket.name); + } + }); + }); + }); + } + + transferTask() { + if(this.workbasket) { + this.workbaskets.forEach(workbasket => { + if (workbasket.name == this.workbasket) { + this.workbasketId = workbasket.id; + } + }); + this.restConnectorService.transferTask(this.task.id, this.workbasketId).subscribe( + task => {this.task = task}); + this.router.navigate(['tasks/']); + } + } + + cancelTask() { + this.router.navigate(['tasks/']); + } + + completeTask() { + this.restConnectorService.completeTask(this.task.id).subscribe( + task => {this.task = task}); + this.router.navigate(['tasks/']); + } +} diff --git a/workplace/src/app/taskdetails/taskdetails.component.css b/workplace/src/app/taskdetails/taskdetails.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/workplace/src/app/taskdetails/taskdetails.component.html b/workplace/src/app/taskdetails/taskdetails.component.html new file mode 100644 index 000000000..7b2370c78 --- /dev/null +++ b/workplace/src/app/taskdetails/taskdetails.component.html @@ -0,0 +1,20 @@ +
+
+ Details for task '{{ task.name }}' (ID={{ task.id }}) +
+
+

{{ task.description }}

+

Owner: {{ task.owner }}

+

Tenant ID: {{ task.tenantId }}

+

Creation Date: {{ task.created | date:'medium' }}

+

Claim Date: {{ task.claimed | date:'medium' }}

+

Completion Date: {{ task.completed | date:'medium' }}

+

Modification Date: {{ task.modified | date:'medium' }}

+

Planning Date: {{ task.planned | date:'medium' }}

+

Due Date: {{ task.due | date:'medium' }}

+

Priority: {{ task.priority }}

+

State: {{ task.state }}

+

Type: {{ task.type }}

+

Workbasket: {{ task.workbasket }}

+
+
diff --git a/workplace/src/app/taskdetails/taskdetails.component.spec.ts b/workplace/src/app/taskdetails/taskdetails.component.spec.ts new file mode 100644 index 000000000..b70f0a3a3 --- /dev/null +++ b/workplace/src/app/taskdetails/taskdetails.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TaskdetailsComponent } from './taskdetails.component'; + +describe('TaskdetailsComponent', () => { + let component: TaskdetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TaskdetailsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TaskdetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/workplace/src/app/taskdetails/taskdetails.component.ts b/workplace/src/app/taskdetails/taskdetails.component.ts new file mode 100644 index 000000000..527911948 --- /dev/null +++ b/workplace/src/app/taskdetails/taskdetails.component.ts @@ -0,0 +1,20 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Task } from '../model/task'; + +@Component({ + selector: 'taskdetails', + templateUrl: './taskdetails.component.html', + styleUrls: ['./taskdetails.component.css'] +}) +export class TaskdetailsComponent implements OnInit { + + @Input() + task: Task = null; + + constructor() { + } + + ngOnInit() { + } + +} diff --git a/workplace/src/app/tasklist/tasklist.component.css b/workplace/src/app/tasklist/tasklist.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/workplace/src/app/tasklist/tasklist.component.html b/workplace/src/app/tasklist/tasklist.component.html new file mode 100644 index 000000000..b4b6c56c8 --- /dev/null +++ b/workplace/src/app/tasklist/tasklist.component.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + +
IDNameDue DatePriorityStateActions
{{ task.id }}{{ task.name }}{{ task.due | date:'medium' }}{{ task.priority }}{{ task.state }} + + +
\ No newline at end of file diff --git a/workplace/src/app/tasklist/tasklist.component.spec.ts b/workplace/src/app/tasklist/tasklist.component.spec.ts new file mode 100644 index 000000000..d73c8d2fe --- /dev/null +++ b/workplace/src/app/tasklist/tasklist.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TasklistComponent } from './tasklist.component'; + +describe('TasklistComponent', () => { + let component: TasklistComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TasklistComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TasklistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/workplace/src/app/tasklist/tasklist.component.ts b/workplace/src/app/tasklist/tasklist.component.ts new file mode 100644 index 000000000..d3da8545f --- /dev/null +++ b/workplace/src/app/tasklist/tasklist.component.ts @@ -0,0 +1,39 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Task } from '../model/task'; +import { Router } from '@angular/router'; +import { RestConnectorService } from '../services/rest-connector.service'; + +@Component({ + selector: 'tasklist', + templateUrl: './tasklist.component.html', + styleUrls: ['./tasklist.component.css'] +}) +export class TasklistComponent implements OnInit { + + private columnForOrdering: string; + + @Output() + task = new EventEmitter(); + + @Input() tasks: Task[]; + + constructor(private restConnectorService:RestConnectorService, private router:Router) { + this.columnForOrdering = 'id'; // default: order tasks by id + } + + ngOnInit() { + } + + selectTask(task: Task) { + this.task.next(task); + } + + orderTasks(column:string) { + this.columnForOrdering = column; + } + + openTask(id: string) { + this.restConnectorService.claimTask(id).subscribe(); + this.router.navigate(['tasks/', id]); + } +} diff --git a/workplace/src/app/tasks/tasks.component.css b/workplace/src/app/tasks/tasks.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/workplace/src/app/tasks/tasks.component.html b/workplace/src/app/tasks/tasks.component.html new file mode 100644 index 000000000..75a42821f --- /dev/null +++ b/workplace/src/app/tasks/tasks.component.html @@ -0,0 +1,26 @@ + +
+
+ +
+
+
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/workplace/src/app/tasks/tasks.component.spec.ts b/workplace/src/app/tasks/tasks.component.spec.ts new file mode 100644 index 000000000..88f4f05f3 --- /dev/null +++ b/workplace/src/app/tasks/tasks.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TasksComponent } from './tasks.component'; + +describe('TasksComponent', () => { + let component: TasksComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TasksComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TasksComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/workplace/src/app/tasks/tasks.component.ts b/workplace/src/app/tasks/tasks.component.ts new file mode 100644 index 000000000..334773dc8 --- /dev/null +++ b/workplace/src/app/tasks/tasks.component.ts @@ -0,0 +1,29 @@ +import { Component, Input } from '@angular/core'; +import { Task } from '../model/task'; +import { environment } from '../../environments/environment'; + +@Component({ + selector: 'app-tasks', + templateUrl: './tasks.component.html', + styleUrls: ['./tasks.component.css'] +}) +export class TasksComponent { + + adminUrl: string = environment.taskanaAdminUrl; + monitorUrl: string = environment.taskanaMonitorUrl; + + @Input() + tasks: Task[]; + + @Input() + task: Task; + + loadTasks(tasks: Task[]){ + this.tasks = tasks; + } + + selectTask(task: Task){ + this.task = task; + } + +} diff --git a/workplace/src/app/util/orderTasksBy.pipe.ts b/workplace/src/app/util/orderTasksBy.pipe.ts new file mode 100644 index 000000000..0d2758cf5 --- /dev/null +++ b/workplace/src/app/util/orderTasksBy.pipe.ts @@ -0,0 +1,32 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import { Task } from '../model/task'; + +@Pipe({ + name: 'orderTasksBy' +}) +export class OrderTasksByPipe implements PipeTransform { + + transform(value:Task[], column:string) { + if(value === null) return null; + value.sort((a, b) => { + if(typeof a[column] === 'string') return _compareString(a[column], b[column]); + else return _compareNumber(a[column], b[column]); + }); + return value; + + function _compareString(a:string, b:string): number { + if (a.toLowerCase() < b.toLowerCase()) { + return -1; + } else if (a.toLowerCase() > b.toLowerCase()) { + return 1; + } else { + return 0; + } + } + + function _compareNumber(a:number, b:number): number { + return _compareString(a.toString(), b.toString()); + } + } +} \ No newline at end of file diff --git a/workplace/src/app/workbasket-selector/workbasket-selector.component.css b/workplace/src/app/workbasket-selector/workbasket-selector.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/workplace/src/app/workbasket-selector/workbasket-selector.component.html b/workplace/src/app/workbasket-selector/workbasket-selector.component.html new file mode 100644 index 000000000..de7e3be0c --- /dev/null +++ b/workplace/src/app/workbasket-selector/workbasket-selector.component.html @@ -0,0 +1,15 @@ +
+
+
+ + + + +
+ +
+ +
+ + + \ No newline at end of file diff --git a/workplace/src/app/workbasket-selector/workbasket-selector.component.spec.ts b/workplace/src/app/workbasket-selector/workbasket-selector.component.spec.ts new file mode 100644 index 000000000..4181ae773 --- /dev/null +++ b/workplace/src/app/workbasket-selector/workbasket-selector.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SelectorComponent } from './workbasket-selector.component'; + +describe('SelectorComponent', () => { + let component: SelectorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SelectorComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/workplace/src/app/workbasket-selector/workbasket-selector.component.ts b/workplace/src/app/workbasket-selector/workbasket-selector.component.ts new file mode 100644 index 000000000..2f7426839 --- /dev/null +++ b/workplace/src/app/workbasket-selector/workbasket-selector.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { RestConnectorService } from '../services/rest-connector.service'; +import { DataService } from '../services/data.service'; +import { Task } from '../model/task'; +import { Workbasket } from '../model/workbasket'; + +@Component({ + selector: 'workbasket-selector', + templateUrl: './workbasket-selector.component.html', + styleUrls: ['./workbasket-selector.component.css'] +}) +export class SelectorComponent implements OnInit { + + @Output() tasks = new EventEmitter(); + + autoCompleteData: string[] = new Array; + result: string; + resultId: string; + workbaskets: Workbasket[]; + + constructor(private restConnectorService: RestConnectorService, private dataService: DataService) { } + + ngOnInit() { + this.restConnectorService.getAllWorkBaskets().subscribe( w => { + this.workbaskets = w; + this.workbaskets.forEach(workbasket => { + this.autoCompleteData.push(workbasket.name); + }); + }); + if(this.dataService.workbasketId) { + this.getTasks(this.dataService.workbasketId); + this.result = this.dataService.workbasketName; + } + } + + searchBasket() { + if(this.workbaskets) { + this.workbaskets.forEach(workbasket => { + if (workbasket.name == this.result) { + this.resultId = workbasket.id; + } + }); + this.getTasks(this.resultId); + this.dataService.workbasketId = this.resultId; + this.dataService.workbasketName = this.result; + } + } + + getTasks(id: string) { + this.restConnectorService.findTaskWithWorkbaskets(id).subscribe( + tasks2 => {this.tasks.next(tasks2)}); + } +} diff --git a/workplace/src/assets/.gitkeep b/workplace/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/workplace/src/environments/environment.prod.ts b/workplace/src/environments/environment.prod.ts new file mode 100644 index 000000000..7af2914f1 --- /dev/null +++ b/workplace/src/environments/environment.prod.ts @@ -0,0 +1,6 @@ +export const environment = { + production: true, + taskanaRestUrl: 'http://taskana-rest.mybluemix.net', + taskanaAdminUrl: 'http://taskana-admin.mybluemix.net', + taskanaMonitorUrl: 'http://taskana-monitor.mybluemix.net' +}; diff --git a/workplace/src/environments/environment.ts b/workplace/src/environments/environment.ts new file mode 100644 index 000000000..c00e4ce04 --- /dev/null +++ b/workplace/src/environments/environment.ts @@ -0,0 +1,11 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `.angular-cli.json`. + +export const environment = { + production: false, + taskanaRestUrl: 'http://localhost:8080', + taskanaAdminUrl: 'http://localhost:4201', + taskanaMonitorUrl: 'http://localhost:4202' +}; diff --git a/workplace/src/favicon.ico b/workplace/src/favicon.ico new file mode 100644 index 000000000..8081c7cea Binary files /dev/null and b/workplace/src/favicon.ico differ diff --git a/workplace/src/index.html b/workplace/src/index.html new file mode 100644 index 000000000..66998248e --- /dev/null +++ b/workplace/src/index.html @@ -0,0 +1,18 @@ + + + + + + + Workplace + + + + + + + + Loading... + + + \ No newline at end of file diff --git a/workplace/src/main.ts b/workplace/src/main.ts new file mode 100644 index 000000000..a9ca1caf8 --- /dev/null +++ b/workplace/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/workplace/src/polyfills.ts b/workplace/src/polyfills.ts new file mode 100644 index 000000000..bc94e7a6d --- /dev/null +++ b/workplace/src/polyfills.ts @@ -0,0 +1,72 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** 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'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/workplace/src/styles.css b/workplace/src/styles.css new file mode 100644 index 000000000..90d4ee007 --- /dev/null +++ b/workplace/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/workplace/src/test.ts b/workplace/src/test.ts new file mode 100644 index 000000000..9bf72267e --- /dev/null +++ b/workplace/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/workplace/src/tsconfig.app.json b/workplace/src/tsconfig.app.json new file mode 100644 index 000000000..5e2507db5 --- /dev/null +++ b/workplace/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/workplace/src/tsconfig.spec.json b/workplace/src/tsconfig.spec.json new file mode 100644 index 000000000..510e3f1fd --- /dev/null +++ b/workplace/src/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es5", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/workplace/src/typings.d.ts b/workplace/src/typings.d.ts new file mode 100644 index 000000000..ef5c7bd62 --- /dev/null +++ b/workplace/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/workplace/tsconfig.json b/workplace/tsconfig.json new file mode 100644 index 000000000..a35a8ee3a --- /dev/null +++ b/workplace/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "baseUrl": "src", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2016", + "dom" + ] + } +} diff --git a/workplace/tslint.json b/workplace/tslint.json new file mode 100644 index 000000000..97adaa5e9 --- /dev/null +++ b/workplace/tslint.json @@ -0,0 +1,130 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs" + ], + "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, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +}