diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 76087ef..0000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -target/ -.classpath -.project -.settings/ -.flattened-pom.xml -.idea/* -!/.idea/runConfigurations -/.idea/runConfigurations/* -*.iml -.terraform -terraform.tfstate.backup - -#Vert.X Cache -.vertx/ diff --git a/Acknowledgments.md b/Acknowledgments.md index 1a89003..92c434c 100644 --- a/Acknowledgments.md +++ b/Acknowledgments.md @@ -12,6 +12,7 @@ A full list of all contained components and licenses you can find in the disclos ## Assets overview The following table gives an overview about the in this project used design assets: +======= | Resource | Description |License |Origin | | --------------- |-------------|----------|----------| | /developer-ui-frontend/src/images/addIcon.svg | Add Icon | [Apache License 2.0][1] | [Material.io][2]| @@ -72,6 +73,8 @@ The following table gives an overview about the in this project used design asse | /developer-ui-frontend/src/images/gatewayIcon.svg | Router | [Apache License 2.0][1] | [Material.io][2]| | /developer-ui-frontend/src/images/codeIcon.svg | Code | [Apache License 2.0][1] | [Material.io][2]| | /developer-ui-frontend/src/images/arrow-dropdown.svg | Code | [Apache License 2.0][1] | [Material.io][2]| +| /developer-ui-frontend/src/images/addBox.svg | Add Box| [Apache License 2.0][1] | [Material.io][2]| +| /developer-ui-frontend/src/images/removeBox.svg | Add Box| [Apache License 2.0][1] | [Material.io][2]| | /devui.png | Key Visual | [Bosch SI Example Code License][5]| Own Creation | /developer-ui-frontend/src/images/jsoneditor-icons.svg | jsoneditor-icons | [Apache License 2.0][8] | [JSON Editor][8]| | /developer-ui-frontend/src/images/fixBugIcon.svg | _edited from bug\_report and build_ | [Apache License 2.0][1] | [Material.io][2]| diff --git a/README.md b/README.md index 646925c..822ac37 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,6 @@ The following features are available: * Register new devices and credentials * View devices and their credentials -## System Requirements -The Hub Developer UI works best using the following system environment: -* Java 8 -* Google Chrome - ## Usage * Download the current release from our github release page: [Releases](https://github.com/bsinno/iot-hub-devui/releases). Alternatively you can clone the repo and build the source code on your own. diff --git a/developer-ui-backend/assembly/assemblyConfig.xml b/developer-ui-backend/assembly/assemblyConfig.xml index ad6c055..6d521ee 100644 --- a/developer-ui-backend/assembly/assemblyConfig.xml +++ b/developer-ui-backend/assembly/assemblyConfig.xml @@ -12,7 +12,7 @@ . - ${project.build.directory}/frontend/webroot/LICENSE.TXT + ${project.build.directory}/frontend/webroot/LICENSE . diff --git a/developer-ui-backend/pom.xml b/developer-ui-backend/pom.xml index 351b926..9d5538b 100644 --- a/developer-ui-backend/pom.xml +++ b/developer-ui-backend/pom.xml @@ -16,10 +16,8 @@ 0-SNAPSHOT 3.5.4 2.9.5 - 0.22.0 1.2.3 - 0.8-M2 - 0.21.0 + 0.8 8088 /liveness /readiness @@ -245,11 +243,6 @@ hono-client ${hono.version} - - org.apache.qpid - proton-j - ${proton.version} - io.vertx vertx-web diff --git a/developer-ui-backend/src/main/docker/docker-compose.yml b/developer-ui-backend/src/main/docker/docker-compose.yml index 1bd0ba3..95ab34b 100644 --- a/developer-ui-backend/src/main/docker/docker-compose.yml +++ b/developer-ui-backend/src/main/docker/docker-compose.yml @@ -17,6 +17,6 @@ services: - HUB_CLIENT_TLS_ENABLED=true - HUB.CLIENT.USERNAME=____________TODO_ENTER_USER_NAME____________ - HUB_CLIENT_PASSWORD=____________TODO_ENTER_PASSWORD____________ - - HUB_DEVICEREGISTRY_DEVICEREGISTRY_URL=https://device-registry.bosch-iot-hub.com + - HUB_DEVICEREGISTRY_DEVICEREGISTRY_URL=https://manage.bosch-iot-hub.com - HUB_DEVICEREGISTRY_USERNAME=____________TODO_ENTER_USER_NAME____________ - HUB_DEVICEREGISTRY_PASSWORD=____________TODO_ENTER_PASSWORD____________ diff --git a/developer-ui-backend/src/main/resources/application.yml b/developer-ui-backend/src/main/resources/application.yml index 251ab09..30df626 100644 --- a/developer-ui-backend/src/main/resources/application.yml +++ b/developer-ui-backend/src/main/resources/application.yml @@ -17,6 +17,6 @@ hub: password: ____________TODO_ENTER_PASSWORD____________ deviceregistry: - url: https://device-registry.bosch-iot-hub.com + url: https://manage.bosch-iot-hub.com username: ____________TODO_ENTER_PASSWORD____________ - password: ____________TODO_ENTER_PASSWORD____________ \ No newline at end of file + password: ____________TODO_ENTER_PASSWORD____________ diff --git a/developer-ui-frontend/LICENSE.TXT b/developer-ui-frontend/LICENSE similarity index 100% rename from developer-ui-frontend/LICENSE.TXT rename to developer-ui-frontend/LICENSE diff --git a/developer-ui-frontend/assembly/assemblyConfig.xml b/developer-ui-frontend/assembly/assemblyConfig.xml index bf10ab2..0cf0ed6 100644 --- a/developer-ui-frontend/assembly/assemblyConfig.xml +++ b/developer-ui-frontend/assembly/assemblyConfig.xml @@ -13,7 +13,7 @@ - ${project.basedir}/LICENSE.TXT + ${project.basedir}/LICENSE . diff --git a/developer-ui-frontend/package.json b/developer-ui-frontend/package.json index 5a787ff..4f5d97e 100644 --- a/developer-ui-frontend/package.json +++ b/developer-ui-frontend/package.json @@ -29,7 +29,7 @@ ] }, "author": "Tim Weise", - "license": "SEE LICENSE IN LICENSE.TXT", + "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@storybook/addon-info": "^3.2.18", "@storybook/addon-options": "^3.2.18", @@ -57,9 +57,9 @@ "eslint-plugin-react": "^5.0.1", "extract-text-webpack-plugin": "^2.1.0", "file-loader": "1.1.6", - "html-webpack-plugin": "^2.28.0", + "html-webpack-plugin": "^3.2.0", "image-webpack-loader": "^3.4.2", - "jest": "^22.0.3", + "jest": "^23.6.0", "node-sass": "^4.5.2", "postcss-flexbugs-fixes": "^3.3.0", "postcss-loader": "^2.0.10", @@ -79,16 +79,18 @@ }, "dependencies": { "@material/typography": "^0.35.0", + "ajv": "6.5.4", + "async": "^2.6.1", "axios": "^0.16.2", "brace": "^0.11.1", + "crypto-js": "^3.1.9-1", "html-react-parser": "^0.4.0", "immutable": "^3.8.1", "jsoneditor-react": "^0.1.9", - "jssha": "^2.3.1", - "lodash": "^4.17.5", "lodash.range": "^3.2.0", "lodash.throttle": "^4.1.1", "moment": "^2.22.2", + "normalizr": "^3.2.4", "polished": "^1.9.3", "pretty-checkbox": "^3.0.3", "prismjs": "^1.8.1", diff --git a/developer-ui-frontend/src/__mocks__/actionMocks.js b/developer-ui-frontend/src/__mocks__/actionMocks.js index 28b1c41..6171d50 100644 --- a/developer-ui-frontend/src/__mocks__/actionMocks.js +++ b/developer-ui-frontend/src/__mocks__/actionMocks.js @@ -15,13 +15,9 @@ import { import { exampleEventBus } from "__mocks__/storeMocks/stateMocks"; import { exampleGetAuthIds } from "__mocks__/apiResponses"; import { fromJS } from "immutable"; -import { - formatDateString, - randomDate, - calculateLogId, - calculateFilterId, - mapCredentialParams -} from "utils"; +import { calculateLogId, calculateFilterId } from "utils"; +import { normalize } from "normalizr"; +import { Credential } from "api/schemas"; export const exampleNewLogAction = { type: actionTypes.NEW_LOG, @@ -116,36 +112,50 @@ export const exampleCalculateLogMemoryAction = { // Initial fetch export const exampleCredentialsFetchedAction = { type: actionTypes.CREDENTIALS_FETCHED, - data: exampleGetAuthIds, - prevAuthIds: fromJS([]) + data: normalize(exampleGetAuthIds, { + credentials: [Credential] + }), + deviceId: exampleGetAuthIds.credentials[0]["device-id"] }; // After fetches were made export const exampleCredentialsFetchedAction2 = { type: actionTypes.CREDENTIALS_FETCHED, - data: exampleGetAuthIds, - prevAuthIds: fromJS([ - "newDevice-97fff113f84b4d91a208889d13236fa8", - "newDevice-f6ca6710e78043568ddf86b70bb4f010" - ]) + data: normalize(exampleGetAuthIds, { + credentials: [Credential] + }), + deviceId: exampleGetAuthIds.credentials[0]["device-id"] }; // First credential got deleted in the meantime export const exampleCredentialsFetchedAction3 = { type: actionTypes.CREDENTIALS_FETCHED, - data: { total: 1, credentials: [exampleGetAuthIds.credentials[1]] }, - prevAuthIds: fromJS([ - "newDevice-97fff113f84b4d91a208889d13236fa8", - "newDevice-f6ca6710e78043568ddf86b70bb4f010" - ]) + data: normalize( + { total: 1, credentials: [exampleGetAuthIds.credentials[1]] }, + { + credentials: [Credential] + } + ), + deviceId: exampleGetAuthIds.credentials[0]["device-id"] }; export const exampleNewCredentialAction = { type: actionTypes.NEW_CREDENTIAL, authId: "newDevice-f6ca6710e78043568ddf86b70bb4f010", - deviceId: "newDevice", - newCredential: mapCredentialParams( - "newDevice-f6ca6710e78043568ddf86b70bb4f010", - "hashed-password", - null - ) + deviceId: "newDevice-f6ca6710e78043568ddf86b70bb4f010", + credential: { + "auth-id": "newDevice-f6ca6710e78043568ddf86b70bb4f010", + type: "hashed-password", + secrets: [ + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=" + ] + }, + secrets: { + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=": { + "hash-function": "sha-512", + "pwd-hash": + "tzmMe0PvXf4mFeY5NTR6g+ICy3beuof/h8TV9Wws3dNRPEt+bWmf2T1pdYIFK+xHB2vBnJ0qoawxREzFwdzMmA==", + secretId: + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=" + } + } }; diff --git a/developer-ui-frontend/src/__mocks__/storeMocks/credentialMocks.js b/developer-ui-frontend/src/__mocks__/storeMocks/credentialMocks.js index 18c7b74..25abee7 100644 --- a/developer-ui-frontend/src/__mocks__/storeMocks/credentialMocks.js +++ b/developer-ui-frontend/src/__mocks__/storeMocks/credentialMocks.js @@ -3,172 +3,9 @@ */ import { fromJS } from "immutable"; -export const hubDevPresetCredentials = fromJS({ - byId: { - "2347-df534b6c45634615b68896c6d42479bc": { - authId: "2347-df534b6c45634615b68896c6d42479bc", - type: "hashed-password", - secrets: ["2347-df534b6c45634615b68896c6d42479bc-1"] - }, - "4711-1abd104378cc43a3b729f309b0af36f6": { - authId: "4711-1abd104378cc43a3b729f309b0af36f6", - type: "hashed-password", - secrets: ["4711-1abd104378cc43a3b729f309b0af36f6-1"] - }, - "4717-24b586f992a347a4b1f954258dfd9cf9": { - authId: "4717-24b586f992a347a4b1f954258dfd9cf9", - type: "hashed-password", - secrets: ["4717-24b586f992a347a4b1f954258dfd9cf9-1"] - }, - "4727-f251b7c503b248569c6e883325c8e0d2": { - authId: "4727-f251b7c503b248569c6e883325c8e0d2", - type: "hashed-password", - secrets: [ - "4727-f251b7c503b248569c6e883325c8e0d2-1", - "4727-f251b7c503b248569c6e883325c8e0d2-2" - ] - }, - "4729-7d243e0ae00e42d0815ac87ddfb753e3": { - authId: "4729-7d243e0ae00e42d0815ac87ddfb753e3", - type: "hashed-password", - secrets: ["4729-7d243e0ae00e42d0815ac87ddfb753e3-1"] - }, - "4731-6310711012df48fb9a1006c67766a295": { - authId: "4731-6310711012df48fb9a1006c67766a295", - type: "hashed-password", - secrets: ["4731-6310711012df48fb9a1006c67766a295-1"] - }, - "4732-1d2430358ea54afbb2150ee5e57a70fc": { - authId: "4732-1d2430358ea54afbb2150ee5e57a70fc", - type: "hashed-password", - secrets: ["4732-1d2430358ea54afbb2150ee5e57a70fc-1"] - }, - "4746-c611002f97be4ddfb46dab94aa8fa70a": { - authId: "4746-c611002f97be4ddfb46dab94aa8fa70a", - type: "hashed-password", - secrets: ["4746-c611002f97be4ddfb46dab94aa8fa70a-1"] - }, - "4780-4cb2dd19ecd54c15a510d7e9c261ec0d": { - authId: "4780-4cb2dd19ecd54c15a510d7e9c261ec0d", - type: "hashed-password", - secrets: ["4780-4cb2dd19ecd54c15a510d7e9c261ec0d-1"] - }, - "HubTester-a8016401d65d4c6783aaf1efe2f466a7": { - authId: "HubTester-a8016401d65d4c6783aaf1efe2f466a7", - type: "hashed-password", - secrets: ["HubTester-a8016401d65d4c6783aaf1efe2f466a7-1"] - }, - "my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a": { - authId: "my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a", - type: "hashed-password", - secrets: ["my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a-1"] - } - }, - allIds: [ - "2347-df534b6c45634615b68896c6d42479bc", - "4711-1abd104378cc43a3b729f309b0af36f6", - "4717-24b586f992a347a4b1f954258dfd9cf9", - "4727-f251b7c503b248569c6e883325c8e0d2", - "4729-7d243e0ae00e42d0815ac87ddfb753e3", - "4731-6310711012df48fb9a1006c67766a295", - "4732-1d2430358ea54afbb2150ee5e57a70fc", - "4746-c611002f97be4ddfb46dab94aa8fa70a", - "4780-4cb2dd19ecd54c15a510d7e9c261ec0d", - "HubTester-a8016401d65d4c6783aaf1efe2f466a7", - "my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a" - ], - secrets: { - byId: { - "2347-df534b6c45634615b68896c6d42479bc-1": { - secretId: "2347-df534b6c45634615b68896c6d42479bc-1", - "hash-function": "sha-512", - "pwd-hash": - "b27fa26912256f92565faa2e4bdd0c4a39c5cc13a698df360c28c8c4f243e8fa653bb13fe28fd3a7942e69fbbeca9441903de0066af49c55f2ff97e8e67e7e63" - }, - "4711-1abd104378cc43a3b729f309b0af36f6-1": { - secretId: "4711-1abd104378cc43a3b729f309b0af36f6-1", - "hash-function": "sha-1", - "pwd-hash": "35619072dbac1a8e6ea9e9c0288efc0fbe8648de" - }, - "4717-24b586f992a347a4b1f954258dfd9cf9-1": { - secretId: "4717-24b586f992a347a4b1f954258dfd9cf9-1", - "hash-function": "sha-256", - "pwd-hash": - "fa639a9a5db0a932f92399ee41d28e54332c6b2c808aa6f6a49a3fe0f652aa4d" - }, - "4727-f251b7c503b248569c6e883325c8e0d2-1": { - secretId: "4727-f251b7c503b248569c6e883325c8e0d2-1", - "hash-function": "sha-256", - "pwd-hash": - "d8d701617d21ae1a0f413c3eb95219ca52fe91484a35fa5c56897a408048dd04" - }, - "4727-f251b7c503b248569c6e883325c8e0d2-2": { - secretId: "4727-f251b7c503b248569c6e883325c8e0d2-2", - "hash-function": "sha-256", - "pwd-hash": - "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b" - }, - "4729-7d243e0ae00e42d0815ac87ddfb753e3-1": { - secretId: "4729-7d243e0ae00e42d0815ac87ddfb753e3-1", - "hash-function": "sha-256", - "pwd-hash": - "03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4" - }, - "4731-6310711012df48fb9a1006c67766a295-1": { - secretId: "4731-6310711012df48fb9a1006c67766a295-1", - "hash-function": "sha-256", - "pwd-hash": - "ac10be4bc110d34370d1ac6977e78fe122a3babaa4641a7977bbfff61b4b6dcd" - }, - "4732-1d2430358ea54afbb2150ee5e57a70fc-1": { - secretId: "4732-1d2430358ea54afbb2150ee5e57a70fc-1", - "hash-function": "sha-256", - "pwd-hash": - "33c5ebbb01d608c254b3b12413bdb03e46c12797e591770ccf20f5e2819929b2" - }, - "4746-c611002f97be4ddfb46dab94aa8fa70a-1": { - secretId: "4746-c611002f97be4ddfb46dab94aa8fa70a-1", - "hash-function": "sha-256", - "pwd-hash": - "a148bc748f1ebb46168f5b542c17a4d9046171e0c7ffdfdcb858597600940e3f" - }, - "4780-4cb2dd19ecd54c15a510d7e9c261ec0d-1": { - secretId: "4780-4cb2dd19ecd54c15a510d7e9c261ec0d-1", - "hash-function": "sha-1", - "pwd-hash": "46babe61e2ca39c790c4524f4ba42e78a4cdece9" - }, - "HubTester-a8016401d65d4c6783aaf1efe2f466a7-1": { - secretId: "HubTester-a8016401d65d4c6783aaf1efe2f466a7-1", - "hash-function": "sha-1", - "pwd-hash": "752bb3ff18055fefcddfcd87881fce12273ddf47" - }, - "my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a-1": { - secretId: "my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a-1", - "hash-function": "sha-1", - "pwd-hash": "5395ebfd174b0a5617e6f409dfbb3e064e3fdf0a" - } - }, - allIds: [ - "2347-df534b6c45634615b68896c6d42479bc-1", - "4711-1abd104378cc43a3b729f309b0af36f6-1", - "4717-24b586f992a347a4b1f954258dfd9cf9-1", - "4727-f251b7c503b248569c6e883325c8e0d2-1", - "4727-f251b7c503b248569c6e883325c8e0d2-2", - "4729-7d243e0ae00e42d0815ac87ddfb753e3-1", - "4731-6310711012df48fb9a1006c67766a295-1", - "4732-1d2430358ea54afbb2150ee5e57a70fc-1", - "4746-c611002f97be4ddfb46dab94aa8fa70a-1", - "4780-4cb2dd19ecd54c15a510d7e9c261ec0d-1", - "HubTester-a8016401d65d4c6783aaf1efe2f466a7-1", - "my-test-device-f8a0fcb9943c4842a3a2536d8e9c6f4a-1" - ] - } -}); - export const credentialsAfterFirstGet = fromJS({ byId: { "newDevice-97fff113f84b4d91a208889d13236fa8": { - "device-id": "newDevice", type: "hashed-password", "auth-id": "newDevice-97fff113f84b4d91a208889d13236fa8", enabled: true, @@ -177,7 +14,6 @@ export const credentialsAfterFirstGet = fromJS({ ] }, "newDevice-f6ca6710e78043568ddf86b70bb4f010": { - "device-id": "newDevice", type: "hashed-password", "auth-id": "newDevice-f6ca6710e78043568ddf86b70bb4f010", enabled: true, @@ -194,25 +30,25 @@ export const credentialsAfterFirstGet = fromJS({ secrets: { byId: { "newDevice-97fff113f84b4d91a208889d13236fa8-vDUuwVzGTvVNZzVok28gimAZmQg=": { - secretId: - "newDevice-97fff113f84b4d91a208889d13236fa8-vDUuwVzGTvVNZzVok28gimAZmQg=", "hash-function": "sha-512", "pwd-hash": - "b821b1b6e19c9223f00c67d1a140efa26c41d70de72b88b514712a269437446235fbe5170d80da071b6b7f738e070462ac4fc0786f1b47738ff064761c03befb" + "b821b1b6e19c9223f00c67d1a140efa26c41d70de72b88b514712a269437446235fbe5170d80da071b6b7f738e070462ac4fc0786f1b47738ff064761c03befb", + secretId: + "newDevice-97fff113f84b4d91a208889d13236fa8-vDUuwVzGTvVNZzVok28gimAZmQg=" }, "newDevice-f6ca6710e78043568ddf86b70bb4f010-YRJcUBaw5IRyRP02Y5K3XF9aX1w=": { - secretId: - "newDevice-f6ca6710e78043568ddf86b70bb4f010-YRJcUBaw5IRyRP02Y5K3XF9aX1w=", "pwd-hash": "QOnuX1H2FykBfS94voqoKfreuLXSZqXkzjYbxpx4N+TDACDLBMlGcms6I6NviFn1IOnOAOEzf0Wh+vslEnXbdw==", - "hash-function": "sha-512" + "hash-function": "sha-512", + secretId: + "newDevice-f6ca6710e78043568ddf86b70bb4f010-YRJcUBaw5IRyRP02Y5K3XF9aX1w=" }, "newDevice-f6ca6710e78043568ddf86b70bb4f010-A1CZP43xxSViySDKW+u5q/z3KdQ=": { - secretId: - "newDevice-f6ca6710e78043568ddf86b70bb4f010-A1CZP43xxSViySDKW+u5q/z3KdQ=", "hash-function": "sha-512", "pwd-hash": - "YpD78SGHQaYypz2GkPp+qghndcfmVQx/Sk8a4EpNMkUwqEHSHiIL0k8cs57DJOyYxg6ydU47ltP4tI4YBWJ30g==" + "YpD78SGHQaYypz2GkPp+qghndcfmVQx/Sk8a4EpNMkUwqEHSHiIL0k8cs57DJOyYxg6ydU47ltP4tI4YBWJ30g==", + secretId: + "newDevice-f6ca6710e78043568ddf86b70bb4f010-A1CZP43xxSViySDKW+u5q/z3KdQ=" } }, allIds: [ @@ -226,7 +62,6 @@ export const credentialsAfterFirstGet = fromJS({ export const credentialsAfterDeletedCred = fromJS({ byId: { "newDevice-f6ca6710e78043568ddf86b70bb4f010": { - "device-id": "newDevice", type: "hashed-password", "auth-id": "newDevice-f6ca6710e78043568ddf86b70bb4f010", enabled: true, @@ -240,18 +75,18 @@ export const credentialsAfterDeletedCred = fromJS({ secrets: { byId: { "newDevice-f6ca6710e78043568ddf86b70bb4f010-YRJcUBaw5IRyRP02Y5K3XF9aX1w=": { - secretId: - "newDevice-f6ca6710e78043568ddf86b70bb4f010-YRJcUBaw5IRyRP02Y5K3XF9aX1w=", "pwd-hash": "QOnuX1H2FykBfS94voqoKfreuLXSZqXkzjYbxpx4N+TDACDLBMlGcms6I6NviFn1IOnOAOEzf0Wh+vslEnXbdw==", - "hash-function": "sha-512" + "hash-function": "sha-512", + secretId: + "newDevice-f6ca6710e78043568ddf86b70bb4f010-YRJcUBaw5IRyRP02Y5K3XF9aX1w=" }, "newDevice-f6ca6710e78043568ddf86b70bb4f010-A1CZP43xxSViySDKW+u5q/z3KdQ=": { - secretId: - "newDevice-f6ca6710e78043568ddf86b70bb4f010-A1CZP43xxSViySDKW+u5q/z3KdQ=", "hash-function": "sha-512", "pwd-hash": - "YpD78SGHQaYypz2GkPp+qghndcfmVQx/Sk8a4EpNMkUwqEHSHiIL0k8cs57DJOyYxg6ydU47ltP4tI4YBWJ30g==" + "YpD78SGHQaYypz2GkPp+qghndcfmVQx/Sk8a4EpNMkUwqEHSHiIL0k8cs57DJOyYxg6ydU47ltP4tI4YBWJ30g==", + secretId: + "newDevice-f6ca6710e78043568ddf86b70bb4f010-A1CZP43xxSViySDKW+u5q/z3KdQ=" } }, allIds: [ @@ -264,16 +99,27 @@ export const credentialsAfterDeletedCred = fromJS({ export const credentialsAfterNewCred = fromJS({ byId: { "newDevice-f6ca6710e78043568ddf86b70bb4f010": { - "device-id": "newDevice", enabled: true, "auth-id": "newDevice-f6ca6710e78043568ddf86b70bb4f010", type: "hashed-password", - secrets: [] + secrets: [ + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=" + ] } }, allIds: ["newDevice-f6ca6710e78043568ddf86b70bb4f010"], secrets: { - byId: {}, - allIds: [] + byId: { + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=": { + "hash-function": "sha-512", + "pwd-hash": + "tzmMe0PvXf4mFeY5NTR6g+ICy3beuof/h8TV9Wws3dNRPEt+bWmf2T1pdYIFK+xHB2vBnJ0qoawxREzFwdzMmA==", + secretId: + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=" + } + }, + allIds: [ + "newDevice-f6ca6710e78043568ddf86b70bb4f010-owispKt9ltqmX3LG83FxrIrdy/0=" + ] } }); diff --git a/developer-ui-frontend/src/actions/CredentialActions.js b/developer-ui-frontend/src/actions/CredentialActions.js index 50982e5..21d33df 100644 --- a/developer-ui-frontend/src/actions/CredentialActions.js +++ b/developer-ui-frontend/src/actions/CredentialActions.js @@ -1,40 +1,24 @@ /* * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. */ -import { - CREATING_CREDENTIAL, - NEW_CREDENTIAL, - CREATING_CREDENTIAL_FAILED, - CREATING_SECRET, - NEW_SECRET, - CREATING_SECRET_FAILED, - NOT_ALLOWED_CREATING_SECRET, - INIT_EMPTY_CREDENTIAL, - DELETING_SECRET, - SECRET_DELETED, - DELETING_SECRET_FAILED, - DELETING_CREDENTIAL, - CREDENTIAL_DELETED, - DELETING_CREDENTIAL_FAILED, - UPDATING_CRED_INFO, - UPDATED_CRED_INFO, - UPDATING_CRED_INFO_FAILED -} from "./actionTypes"; +import * as actionTypes from "./actionTypes"; import { selectCredentialById, selectSecretById, - selectCredentialApiFormat, - selectCredentialIdsByDeviceId + selectDenormalizedCredential, + selectCredentialIdsByDeviceId, + selectSecretsByCredentialId } from "reducers/selectors"; +import { fetchCredentialsByDeviceId } from "actions/DataFetchActions"; import { RESTSERVER_URL } from "_APP_CONSTANTS"; -import { mapCredentialParams, mapSecretParams } from "utils"; +import { Credential, Secret } from "api/schemas"; import axios from "axios"; import _ from "lodash"; -import { selectSecretsByCredentialId } from "reducers/selectors"; +import { normalize } from "normalizr"; export function creatingSecret(authId, secret) { return { - type: CREATING_SECRET, + type: actionTypes.CREATING_SECRET, authId, secret }; @@ -42,7 +26,7 @@ export function creatingSecret(authId, secret) { export function newSecretCreated(authId, secret) { return { - type: NEW_SECRET, + type: actionTypes.NEW_SECRET, authId, secret }; @@ -50,7 +34,7 @@ export function newSecretCreated(authId, secret) { export function creatingSecretFailed(authId, secret) { return { - type: CREATING_SECRET_FAILED, + type: actionTypes.CREATING_SECRET_FAILED, authId, secret }; @@ -58,26 +42,31 @@ export function creatingSecretFailed(authId, secret) { export function notAllowedCreatingSecret(authId, secret) { return { - type: NOT_ALLOWED_CREATING_SECRET, + type: actionTypes.NOT_ALLOWED_CREATING_SECRET, authId, secret }; } -export function createNewSecret(deviceId, authId, secretType, secretId, data) { +export function createNewSecret(deviceId, authId, secret) { return (dispatch, getState) => { + const denormalizedSecret = Object.assign({}, secret); const numberOfSecrets = selectSecretsByCredentialId(getState(), deviceId) .size; // 1. Create a new Secret Object as it's stored in the Redux Store - const newSecret = mapSecretParams(secretId, authId, secretType, data); + const newSecret = Object.values( + normalize( + { "auth-id": authId, secret: denormalizedSecret }, + { secret: Secret } + ).entities.secrets + )[0]; // 2. Flatten the normalized form and add the secret to the secrets array of the credential - const modifiedCredential = selectCredentialById(getState(), authId).toJS(); - modifiedCredential.secrets = modifiedCredential.secrets.map(id => - selectSecretById(getState(), id) - .delete("secretId") - .toJS() - ); - modifiedCredential.secrets.push(_.omit(newSecret, "secretId")); + const modifiedCredential = selectDenormalizedCredential( + getState(), + deviceId, + authId + ).toJS(); + modifiedCredential.secrets.push(denormalizedSecret); // Use the denormalized secret (Without the secretId) // 3. Add the required fields to match the API schema const requestBody = { ...modifiedCredential, "device-id": deviceId }; // 4. Start the regular XHR handling with the updated credential as PUT request @@ -99,44 +88,32 @@ export function createNewSecret(deviceId, authId, secretType, secretId, data) { }; } -export function updatingCredentialInfo(authId, enabled, data) { +export function changedEnabled(authId, enabled) { return { - type: UPDATING_CRED_INFO, + type: actionTypes.ENABLED_CRED_CHANGED, authId, - enabled, - data + enabled }; } -export function updatedCredentialInfo(authId, enabled, data) { +export function changingEnabled(authId, enabled) { return { - type: UPDATED_CRED_INFO, + type: actionTypes.UPDATED_CRED_INFO, authId, - enabled, - data + enabled }; } -export function updatingCredentialInfoFailed(authId, enabled, data) { +export function changingEnabledFailed(authId, enabled) { return { - type: UPDATING_CRED_INFO_FAILED, + type: actionTypes.UPDATING_CRED_INFO_FAILED, authId, - enabled, - data + enabled }; } -export function updateCredentialInfo(data) { +export function changeEnabled(deviceId, authId, enabled) { return (dispatch, getState) => { - // Destructure the received data and extract the optional fields from the required fields - const { - "device-id": deviceId, - type, - "auth-id": authId, - enabled, - secrets, - ...optional - } = data; // Flatten the normalized form and delete the current credential info const modifiedCredential = selectCredentialById(getState(), authId).toJS(); modifiedCredential.secrets = modifiedCredential.secrets.map(id => @@ -150,101 +127,103 @@ export function updateCredentialInfo(data) { // Add the required fields to match the API schema const requestBody = { "device-id": deviceId, - ...modifiedCredential, - ...optional + ...modifiedCredential }; // Start the regular XHR handling with the updated credential as PUT request const tenant = getState().getIn(["settings", "tenant"]); - dispatch(updatingCredentialInfo(authId, enabled, optional)); + const info = selectDenormalizedCredential(getState(), deviceId, authId) + .set("enabled", enabled) + .toJS(); return axios .put(`${RESTSERVER_URL}/credentials/${tenant}`, requestBody) - .then(() => dispatch(updatedCredentialInfo(authId, enabled, optional))) + .then(() => dispatch(fetchCredentialsByDeviceId(deviceId))) .catch(err => { - dispatch(updatingCredentialInfoFailed(authId, enabled, optional)); + dispatch(changingEnabledFailed(authId, info)); console.error(err); }); }; } -export function newCredentialCreated(authId, deviceId, newCredential) { +export function newCredentialCreated(authId, deviceId, credential, secrets) { return { - type: NEW_CREDENTIAL, + type: actionTypes.NEW_CREDENTIAL, authId, deviceId, - newCredential + credential, + secrets }; } // An empty credential is not yet created and needs a secret before it's sent to the backend -export function initializeEmptyCredential(authId, credType, deviceId, data) { - const newCredential = mapCredentialParams(authId, credType, data); +export function initializeEmptyCredential(credential) { + const newAuthId = credential["auth-id"]; return { - type: INIT_EMPTY_CREDENTIAL, - authId, - deviceId, - newCredential + type: actionTypes.INIT_EMPTY_CREDENTIAL, + credential: normalize(credential, Credential).entities.credentials[ + newAuthId + ], + deviceId: credential["device-id"] }; } -export function creatingNewCredential(authId, deviceId, newCredential) { +export function creatingNewCredential(authId, deviceId, credential, secrets) { return { - type: CREATING_CREDENTIAL, + type: actionTypes.CREATING_CREDENTIAL, authId, deviceId, - newCredential + credential, + secrets }; } -export function creatingNewCredentialFailed(authId, deviceId, newCredential) { +export function creatingNewCredentialFailed( + authId, + deviceId, + credential, + secrets +) { return { - type: CREATING_CREDENTIAL_FAILED, + type: actionTypes.CREATING_CREDENTIAL_FAILED, authId, deviceId, - newCredential + credential, + secrets }; } -// This function creates a new Credential with a new Secret on the backend (The API does not distinguish between -// credentials and secrets) +// This function creates a new Credential with a new Secret in the IoT Hub Device Registry // It could be called after an empty credential was initialized and a firstInitialization // has been done (the first secret was added to the empty credential) or after a quickstart like // from the action createStandardPasswordRegistration // (in this case, the credential and the secret get created in one step) -// The secretId Argument is optional (if not defined it's generated by mapSecretParams) -// TODO: Refactoring - Too many parameters + make it possible to create new credentials with many secrets -export function createNewCredential( - authId, - credType, - deviceId, - data, - secretId, - secretType, - secretData -) { +export function createNewCredential(credential) { + const denormalizedCredential = { ...credential }; + const { "auth-id": authId, "device-id": deviceId } = credential; return (dispatch, getState) => { const tenant = getState().getIn(["settings", "tenant"]); - // 1. Create new Credential Object as it's stored in the Redux store - const newCredential = mapCredentialParams(authId, credType, data); - // 2. Create new secret for the new credential - const newSecret = mapSecretParams(secretId, authId, secretType, secretData); - // 3. Add the required fields to match the API schema - const requestBody = Object.assign( - { - "device-id": deviceId - }, - { ...newCredential, secrets: [_.omit(newSecret, "secretId")] } + // Normalize the credential with Credential and Secret to store in the Redux store + const normalized = normalize(credential, Credential); + const newCredential = normalized.entities.credentials[authId]; + const newSecrets = normalized.entities.secrets; + dispatch( + creatingNewCredential(authId, deviceId, newCredential, newSecrets) ); - // 4. Start the regular XHR handling with the new credential as POST request - dispatch(creatingNewCredential(authId, deviceId, newCredential)); return axios - .post(`${RESTSERVER_URL}/credentials/${tenant}`, requestBody) + .post(`${RESTSERVER_URL}/credentials/${tenant}`, denormalizedCredential) .then(() => { - dispatch(newCredentialCreated(authId, deviceId, newCredential)); - // This also adds the new secret - dispatch(newSecretCreated(authId, newSecret)); + dispatch( + newCredentialCreated(authId, deviceId, newCredential, newSecrets) + ); }) .catch(err => { - dispatch(creatingNewCredentialFailed(authId, deviceId, newCredential)); + dispatch( + creatingNewCredentialFailed( + authId, + deviceId, + newCredential, + newSecrets + ) + ); console.error(err); }); }; @@ -252,7 +231,7 @@ export function createNewCredential( export function deletingSecret(deviceId, authId, secretId) { return { - type: DELETING_SECRET, + type: actionTypes.DELETING_SECRET, deviceId, authId, secretId @@ -261,7 +240,7 @@ export function deletingSecret(deviceId, authId, secretId) { export function secretDeleted(deviceId, authId, secretId) { return { - type: SECRET_DELETED, + type: actionTypes.SECRET_DELETED, deviceId, authId, secretId @@ -270,7 +249,7 @@ export function secretDeleted(deviceId, authId, secretId) { export function deletingSecretFailed(deviceId, authId, secretId) { return { - type: DELETING_SECRET_FAILED, + type: actionTypes.DELETING_SECRET_FAILED, deviceId, authId, secretId @@ -282,7 +261,7 @@ export function deleteSecret(deviceId, authId, secretId) { const deletedSecret = selectSecretById(getState(), secretId).delete( "secretId" ); - const currentCredential = selectCredentialApiFormat( + const currentCredential = selectDenormalizedCredential( getState(), deviceId, authId @@ -290,7 +269,7 @@ export function deleteSecret(deviceId, authId, secretId) { const modifiedCredential = currentCredential .update( "secrets", - secrets => secrets.filter(secret => secret.equals(deletedSecret)) // Uses efficient deep Object Comparison (Immutable.js Maps) + secrets => secrets.filter(secret => !secret.equals(deletedSecret)) // Uses efficient deep Object Comparison (Immutable.js Maps) ) .toJS(); const tenant = getState().getIn(["settings", "tenant"]); @@ -307,7 +286,7 @@ export function deleteSecret(deviceId, authId, secretId) { export function deletingCredential(deviceId, authId) { return { - type: DELETING_CREDENTIAL, + type: actionTypes.DELETING_CREDENTIAL, deviceId, authId }; @@ -315,7 +294,7 @@ export function deletingCredential(deviceId, authId) { export function credentialDeleted(deviceId, authId) { return { - type: CREDENTIAL_DELETED, + type: actionTypes.CREDENTIAL_DELETED, deviceId, authId }; @@ -323,7 +302,7 @@ export function credentialDeleted(deviceId, authId) { export function deletingCredentialFailed(deviceId, authId) { return { - type: DELETING_CREDENTIAL_FAILED, + type: actionTypes.DELETING_CREDENTIAL_FAILED, deviceId, authId }; @@ -359,8 +338,120 @@ export function deleteCredential(deviceId, authId) { export function deleteAllCredentialsOfDevice(deviceId) { return (dispatch, getState) => { const credentials = selectCredentialIdsByDeviceId(getState(), deviceId); + console.log("credentials", credentials); return Promise.all( credentials.map(id => dispatch(deleteCredential(deviceId, id))) ); }; } + +export function updatingCredentialSecrets(authId, info) { + return { + type: actionTypes.UPDATING_CRED_SECRETS, + authId, + info + }; +} + +export function updatedCredentialSecrets(authId, newSecrets) { + return { + type: actionTypes.UPDATED_CRED_SECRETS, + authId, + newSecrets + }; +} + +export function updatingCredentialSecretsFailed(authId, info) { + return { + type: actionTypes.UPDATING_CRED_SECRETS_FAILED, + authId, + info + }; +} + +export function changingSecretsInfo(authId, info) { + return { + type: actionTypes.CHANGING_CRED_SECRETS, + authId, + info + }; +} + +export function updatingCredentialInfoFailed(authId, enabled, data) { + return { + type: actionTypes.UPDATING_CRED_INFO_FAILED, + authId, + enabled, + data + }; +} + +export function updateCredentialInfo(data) { + return (dispatch, getState) => { + // Destructure the received data and extract the optional fields from the required fields + const { + "device-id": deviceId, + type, + "auth-id": authId, + enabled, + secrets, + ...optional + } = data; + // Flatten the normalized form and delete the current credential info + const modifiedCredential = selectCredentialById(getState(), authId).toJS(); + modifiedCredential.secrets = modifiedCredential.secrets.map(id => + selectSecretById(getState(), id) + .delete("secretId") + .toJS() + ); + delete modifiedCredential.credentialInfo; + // edit the mutable credential Informations (enabled and the optional data) + if (enabled !== undefined) modifiedCredential.enabled = enabled; + if (secrets !== undefined) modifiedCredential.secrets = secrets; + if (optional !== undefined) modifiedCredential.optional = secrets; + // Add the required fields to match the API schema + const requestBody = { + "device-id": deviceId, + ...modifiedCredential, + ...optional + }; + // Start the regular XHR handling with the updated credential as PUT request + const tenant = getState().getIn(["settings", "tenant"]); + dispatch(changingSecretsInfo(authId, enabled, optional)); + return axios + .put(`${RESTSERVER_URL}/credentials/${tenant}`, requestBody) + .then(() => dispatch(updatedCredentialSecrets(authId, enabled, optional))) + .then(() => dispatch(fetchCredentialsByDeviceId(deviceId))) + .catch(err => { + dispatch(updatingCredentialInfoFailed(authId, enabled, optional)); + console.error(err); + }); + }; +} + +export function updateCredentialInfoSecrets(deviceId, authId, info) { + return (dispatch, getState) => { + const tenant = getState().getIn(["settings", "tenant"]); + dispatch(updatingCredentialSecrets(authId, info)); + return axios + .put(`${RESTSERVER_URL}/credentials/${tenant}`, info) + .then(({ data }) => { + dispatch(updatedCredentialSecrets(authId, info.secrets)); + dispatch(fetchCredentialsByDeviceId(deviceId)); + }) + .catch(err => { + dispatch(updatingCredentialSecretsFailed(authId, info)); + console.error(err); + }); + }; +} + +export function changeSecretsInfo(deviceId, authId, newSecrets) { + return (dispatch, getState) => { + dispatch(changingSecretsInfo(authId, newSecrets)); + const info = selectDenormalizedCredential(getState(), deviceId, authId) + .set("secrets", newSecrets) + .toJS(); + return dispatch(updateCredentialInfoSecrets(deviceId, authId, info)); + }; +} diff --git a/developer-ui-frontend/src/actions/DataFetchActions.js b/developer-ui-frontend/src/actions/DataFetchActions.js index b27f40f..b84d03a 100644 --- a/developer-ui-frontend/src/actions/DataFetchActions.js +++ b/developer-ui-frontend/src/actions/DataFetchActions.js @@ -13,8 +13,9 @@ import { FETCHING_CREDENTIALS_FAILED } from "actions/actionTypes"; import { RESTSERVER_URL } from "_APP_CONSTANTS"; -import { arrayToObject } from "utils"; import axios from "axios"; +import { normalize } from "normalizr"; +import { Credential, Registration } from "api/schemas"; export function tenantFetched(tenant) { return { @@ -41,27 +42,10 @@ export function fetchingRegistrationsFailed() { }; } -export function registrationsFetched(registrations) { - const mappedFormatArray = registrations.devices.map(device => { - const deviceCopy = Object.assign({}, device); - delete deviceCopy["device-id"]; - deviceCopy.deviceId = device["device-id"]; - const { deviceId, ...other } = deviceCopy; - return { - deviceId, - lastActive: null, - currentlyActive: false, - configuredSubscribed: false, - isSubscribed: false, - logs: [], - credentials: [], - registrationInfo: { ...other } - }; - }); - const mappedFormat = arrayToObject(mappedFormatArray, "deviceId"); +export function registrationsFetched(data) { return { type: REGISTRATIONS_FETCHED, - registrations: mappedFormat + data }; } @@ -72,12 +56,11 @@ export function fetchingCredentials(deviceId) { }; } -export function credentialsFetched(data, deviceId, prevAuthIds) { +export function credentialsFetched(data, deviceId) { return { type: CREDENTIALS_FETCHED, data, - deviceId, - prevAuthIds + deviceId }; } @@ -107,7 +90,12 @@ export function fetchAllRegistrations() { const tenant = getState().getIn(["settings", "tenant"]); return axios .get(`${RESTSERVER_URL}/registration/${tenant}`) - .then(({ data }) => dispatch(registrationsFetched(data))) + .then(({ data }) => { + const normalizedData = normalize(data, { + devices: [Registration] + }); + dispatch(registrationsFetched(normalizedData)); + }) .catch(err => { dispatch(fetchingRegistrationsFailed()); console.error(err); @@ -124,17 +112,14 @@ export function fetchCredentialsByDeviceId(deviceId) { return (dispatch, getState) => { dispatch(fetchingCredentials(deviceId)); const tenant = getState().getIn(["settings", "tenant"]); - const prevAuthIds = getState().getIn([ - "devices", - "byId", - deviceId, - "credentials" - ]); return axios .get(`${RESTSERVER_URL}/credentials/${tenant}?device-id=${deviceId}`) - .then(({ data }) => - dispatch(credentialsFetched(data, deviceId, prevAuthIds)) - ) + .then(({ data }) => { + const normalizedData = normalize(data, { + credentials: [Credential] + }); + dispatch(credentialsFetched(normalizedData, deviceId)); + }) .catch(err => { dispatch(fetchingCredentialsFailed(deviceId)); console.error(err); diff --git a/developer-ui-frontend/src/actions/RegistrationActions.js b/developer-ui-frontend/src/actions/RegistrationActions.js index df1a5cd..67954ba 100644 --- a/developer-ui-frontend/src/actions/RegistrationActions.js +++ b/developer-ui-frontend/src/actions/RegistrationActions.js @@ -1,32 +1,16 @@ /* * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. */ -import { - CREATING_REG, - NEW_REG, - CREATING_REG_FAILED, - UPDATING_REG_INFO, - UPDATED_REG_INFO, - UPDATING_REG_INFO_FAILED, - REG_DELETED, - DELETING_REG, - DELETING_REG_FAILED, - CHANGING_ENABLED, - ENABLED_CHANGED, - CONFIGURING_GATEWAY, - CONFIGURED_GATEWAY, - CONFIGURING_GATEWAY_FAILED, - SETTED_VIA_PROPERTY, - SETTING_VIA_PROPERTY -} from "actions/actionTypes"; +import * as actionTypes from "actions/actionTypes"; import { selectRegistrationInfo } from "reducers/selectors"; import { createNewCredential } from "./CredentialActions"; +import { hashSecret } from "api/schemas"; import { RESTSERVER_URL } from "_APP_CONSTANTS"; import axios from "axios"; export function configuringNewGateway(deviceId, info) { return { - type: CONFIGURING_GATEWAY, + type: actionTypes.CONFIGURING_GATEWAY, deviceId, info }; @@ -34,7 +18,7 @@ export function configuringNewGateway(deviceId, info) { export function newGatewayConfigured(deviceId, info) { return { - type: CONFIGURED_GATEWAY, + type: actionTypes.CONFIGURED_GATEWAY, deviceId, info }; @@ -42,7 +26,7 @@ export function newGatewayConfigured(deviceId, info) { export function configuringNewGatewayFailed(deviceId, info) { return { - type: CONFIGURING_GATEWAY_FAILED, + type: actionTypes.CONFIGURING_GATEWAY_FAILED, deviceId, info }; @@ -50,7 +34,7 @@ export function configuringNewGatewayFailed(deviceId, info) { export function settingViaProperty(deviceId, gatewayId) { return { - type: SETTING_VIA_PROPERTY, + type: actionTypes.SETTING_VIA_PROPERTY, deviceId, gatewayId }; @@ -58,7 +42,7 @@ export function settingViaProperty(deviceId, gatewayId) { export function settedViaProperty(deviceId, gatewayId) { return { - type: SETTED_VIA_PROPERTY, + type: actionTypes.VIA_PROPERTY_SET, deviceId, gatewayId }; @@ -92,42 +76,29 @@ export function setViaProperty(deviceId, gatewayId) { }; } -export function createdNewRegistration(device, configuredSubscribed) { +export function createdNewRegistration(deviceId, configuredSubscribed) { return { - type: NEW_REG, - deviceId: device.deviceId, - device, + type: actionTypes.NEW_REG, + deviceId, configuredSubscribed }; } export function creatingNewRegistration(deviceId) { return { - type: CREATING_REG, + type: actionTypes.CREATING_REG, deviceId }; } export function creatingRegistrationFailed(deviceId) { return { - type: CREATING_REG_FAILED, + type: actionTypes.CREATING_REG_FAILED, deviceId }; } export function createNewRegistration(deviceId, configuredSubscribed) { - const newReg = { - deviceId, - lastActive: null, - currentlyActive: false, - configuredSubscribed, - isSubscribed: false, - logs: [], - credentials: [], - registrationInfo: { - enabled: true - } - }; const requestBody = { "device-id": deviceId, enabled: true @@ -138,7 +109,7 @@ export function createNewRegistration(deviceId, configuredSubscribed) { return axios .post(`${RESTSERVER_URL}/registration/${tenant}`, requestBody) .then(() => - dispatch(createdNewRegistration(newReg, configuredSubscribed)) + dispatch(createdNewRegistration(deviceId, configuredSubscribed)) ) .catch(err => { dispatch(creatingRegistrationFailed(deviceId)); @@ -149,26 +120,27 @@ export function createNewRegistration(deviceId, configuredSubscribed) { export function deletingRegistration(deviceId) { return { - type: DELETING_REG, + type: actionTypes.DELETING_REG, deviceId }; } export function deletedRegistration(deviceId) { return { - type: REG_DELETED, + type: actionTypes.REG_DELETED, deviceId }; } export function deletingRegistrationFailed(deviceId) { return { - type: DELETING_REG_FAILED, + type: actionTypes.DELETING_REG_FAILED, deviceId }; } export function deleteRegistration(deviceId) { + console.trace(); return (dispatch, getState) => { const tenant = getState().getIn(["settings", "tenant"]); dispatch(deletingRegistration(deviceId)); @@ -184,7 +156,7 @@ export function deleteRegistration(deviceId) { export function updatingRegistrationInfo(deviceId, info) { return { - type: UPDATING_REG_INFO, + type: actionTypes.UPDATING_REG_INFO, deviceId, info }; @@ -192,7 +164,7 @@ export function updatingRegistrationInfo(deviceId, info) { export function updatedRegistrationInfo(deviceId, info) { return { - type: UPDATED_REG_INFO, + type: actionTypes.UPDATED_REG_INFO, deviceId, info }; @@ -200,7 +172,7 @@ export function updatedRegistrationInfo(deviceId, info) { export function updatingRegistrationInfoFailed(deviceId, info) { return { - type: UPDATING_REG_INFO_FAILED, + type: actionTypes.UPDATING_REG_INFO_FAILED, deviceId, info }; @@ -208,14 +180,14 @@ export function updatingRegistrationInfoFailed(deviceId, info) { export function changedEnabled(deviceId) { return { - type: ENABLED_CHANGED, + type: actionTypes.ENABLED_CHANGED, deviceId }; } export function changingEnabled(deviceId, enabled) { return { - type: CHANGING_ENABLED, + type: actionTypes.CHANGING_ENABLED, deviceId, enabled }; @@ -223,6 +195,7 @@ export function changingEnabled(deviceId, enabled) { export function updateRegistrationInfo(deviceId, info, enableChange) { return (dispatch, getState) => { + console.log(info); const tenant = getState().getIn(["settings", "tenant"]); dispatch(updatingRegistrationInfo(deviceId, info)); return axios @@ -256,15 +229,12 @@ export function createStandardPasswordRegistration( return dispatch => { dispatch(createNewRegistration(deviceId, false)).then(() => dispatch( - createNewCredential( - authId, - "hashed-password", - deviceId, - null, - null, - "Hashed Password", - secretData - ) + createNewCredential({ + "auth-id": authId, + type: "hashed-password", + "device-id": deviceId, + secrets: [hashSecret(secretData)] + }) ) ); }; diff --git a/developer-ui-frontend/src/actions/__tests__/FilterActions.test.js b/developer-ui-frontend/src/actions/__tests__/FilterActions.test.js deleted file mode 100644 index e821ec8..0000000 --- a/developer-ui-frontend/src/actions/__tests__/FilterActions.test.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import configureStore from "redux-mock-store"; -import * as filterActions from "../FilterActions"; -import * as actionTypes from "../actionTypes"; -import { exampleFilterDeviceId } from "__mocks__/storeMocks/filterMocks"; -import { calculateFilterId } from "utils"; - -const middlewares = []; -const mockStore = configureStore(middlewares); - -describe("FilterActions Action Creators", () => { - test("newFilter should dispatch an action with the filter argument in the payload", () => { - // Initialize mockstore with empty state - const store = mockStore({}); - // Dispatch the action - store.dispatch(filterActions.newFilter(exampleFilterDeviceId)); - // Test if the store dispatched the expected actions - const actions = store.getActions(); - const expectedPayload = { - type: actionTypes.NEW_FILTER, - filter: exampleFilterDeviceId - }; - expect(actions).toEqual([expectedPayload]); - }); - - test("removeFilter should dispatch an action with the filterId argument in the payload", () => { - const store = mockStore({}); - const filterId = calculateFilterId( - exampleFilterDeviceId.get("type"), - exampleFilterDeviceId.get("value") - ); - store.dispatch(filterActions.removeFilter(filterId)); - const expectedPayload = { - type: actionTypes.REMOVE_FILTER, - filterId - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); -}); diff --git a/developer-ui-frontend/src/actions/__tests__/SubscriptionActions.test.js b/developer-ui-frontend/src/actions/__tests__/SubscriptionActions.test.js deleted file mode 100644 index 715173f..0000000 --- a/developer-ui-frontend/src/actions/__tests__/SubscriptionActions.test.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import configureStore from "redux-mock-store"; -import * as subscriptionActions from "../SubscriptionActions"; -import * as actionTypes from "../actionTypes"; -import { createExampleSubWOLogs } from "__mocks__/storeMocks/deviceMocks"; -import { exampleEventBus } from "__mocks__/storeMocks/stateMocks"; - -const middlewares = []; -const mockStore = configureStore(middlewares); - -describe("SubscriptionActions Action Creators", () => { - test("newSubCreated should dispatch an action with the deviceId argument and the eventBus in the payload", () => { - // The updated instance of the eventBus object is needed as a subscription change also leads to a new websocket channel - // Initialize mockstore with empty state - const store = mockStore({}); - const newSubId = "TestDevice1"; - // Dispatch the action - store.dispatch( - subscriptionActions.newSubCreated(exampleEventBus, newSubId) - ); - // Test if the store dispatched the expected actions - const actions = store.getActions(); - const expectedPayload = { - type: actionTypes.NEW_SUB, - eventBus: exampleEventBus, - deviceId: newSubId - }; - expect(actions).toEqual([expectedPayload]); - }); - - test("subDeleted should dispatch an action with the deviceId argument and the eventBus in the payload", () => { - const store = mockStore({}); - const deletedSubId = "TestDevice1"; - store.dispatch( - subscriptionActions.subDeleted(exampleEventBus, deletedSubId) - ); - const expectedPayload = { - type: actionTypes.SUB_DELETED, - eventBus: exampleEventBus, - deviceId: deletedSubId - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); -}); diff --git a/developer-ui-frontend/src/actions/__tests__/UserSettingsActions.test.js b/developer-ui-frontend/src/actions/__tests__/UserSettingsActions.test.js deleted file mode 100644 index e2f54b9..0000000 --- a/developer-ui-frontend/src/actions/__tests__/UserSettingsActions.test.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import configureStore from 'redux-mock-store'; -import * as userSettingsActions from '../UserSettingsActions'; -import * as actionTypes from '../actionTypes'; -import thunk from 'redux-thunk'; -import { fromJS } from 'immutable'; - -const middlewares = [thunk]; -const mockStore = configureStore(middlewares); - -describe('UserSettingsActions Thunk Action Creators', () => { - const exampleSetting = { - setting: 'bufferSize', - value: 300 // MB - }; - - test('updateSetting of type "bufferSize" should dispatch only one action (SUBMIT_SETTINGS) with the settings argument if the limit is not exceeded yet', () => { - // Initialize mockstore with state - const store = mockStore( - fromJS({ - logs: { - byId: { - /* ... */ - }, - allIds: ['1-123456', '2-123456', '3-123456'] - }, - logMemoryCalculation: { maximumAmountOfLogs: 3 } - }) - ); - const expectedPayload = { - type: actionTypes.SUBMIT_SETTINGS, - setting: exampleSetting - }; - store.dispatch(userSettingsActions.updateSetting(exampleSetting)); - expect(store.getActions()).toEqual([expectedPayload]); - }); - - test('updateSetting of type "bufferSize" should dispatch only one action (SUBMIT_SETTINGS) with the settings argument if the limit is not exceeded yet pt2', () => { - // Initialize mockstore with state - const store = mockStore( - fromJS({ - logs: { - byId: { - /* ... */ - }, - allIds: ['1-123456', '2-123456', '3-123456'] - }, - logMemoryCalculation: { maximumAmountOfLogs: 3 } - }) - ); - // limit is reached but not yet exceeded - const expectedPayload = { - type: actionTypes.SUBMIT_SETTINGS, - setting: exampleSetting - }; - store.dispatch(userSettingsActions.updateSetting(exampleSetting)); - expect(store.getActions()).toEqual([expectedPayload]); - }); - - test('updateSetting should cause as many REMOVE_OLDEST_LOG actions as the difference between logsCount and maximumAmountOfLogs', () => { - const store = mockStore( - fromJS({ - logs: { - byId: { - /* ... */ - }, - allIds: ['1-123456', '2-123456', '3-123456'] - }, - logMemoryCalculation: { maximumAmountOfLogs: 1 } - }) - ); - store.dispatch(userSettingsActions.updateSetting(exampleSetting)); - // Limit of 1 logs is exceeded by 2 -> expect 1 SUBMIT_SETTINGS and 2 REMOVE_OLDEST_LOG dispatches -> 3 actions - expect(store.getActions().length).toEqual(3); - }); - - test('changeSorting should dispatch an action with the category argument in the payload', () => { - const store = mockStore({}); - const passedCategory = 'Device Id'; - store.dispatch(userSettingsActions.changeSorting(passedCategory)); - const expectedPayload = { - type: actionTypes.CHANGE_SORTING, - category: passedCategory - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); -}); diff --git a/developer-ui-frontend/src/actions/__tests__/WebsocketActions.test.js b/developer-ui-frontend/src/actions/__tests__/WebsocketActions.test.js deleted file mode 100644 index 14350da..0000000 --- a/developer-ui-frontend/src/actions/__tests__/WebsocketActions.test.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import configureStore from "redux-mock-store"; -import * as websocketActions from "../WebsocketActions"; -import * as logActions from "../LogActions"; -import * as actionTypes from "../actionTypes"; -import thunk from "redux-thunk"; -import { exampleEventBus } from "__mocks__/storeMocks/stateMocks"; -import { - exampleShortMessageInStore, - exampleLongMessageInStore, - exampleShortMessage -} from "__mocks__/storeMocks/messageMocks"; -import { createExampleSubWOLogs } from "__mocks__/storeMocks/deviceMocks"; -import { AVERAGE_MEMCALC_INTERVAL } from "_APP_CONSTANTS"; -import { fromJS } from "immutable"; - -const middlewares = [thunk]; -const mockStore = configureStore(middlewares); - -describe("WebsocketActions Thunks and Action Creators", () => { - test("connecting should dispatch an action with no payload", () => { - // Initialize mockstore with empty state - const store = mockStore({}); - // Dispatch the action - store.dispatch(websocketActions.connecting()); - // Test if the store dispatched the expected actions - const actions = store.getActions(); - const expectedPayload = { - type: actionTypes.CONNECT_TO_EVENTBUS - }; - expect(actions).toEqual([expectedPayload]); - }); - - test("connected should dispatch an action with the eventBus in the payload", () => { - const store = mockStore({}); - store.dispatch(websocketActions.connected(exampleEventBus)); - const expectedPayload = { - type: actionTypes.EVENTBUS_CONNECTED, - eventBus: exampleEventBus - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); - - test("calculateLogMemory should dispatch an action with allLogs in the payload", () => { - const store = mockStore({}); - const exampleAllLogs = [ - exampleShortMessageInStore, - exampleLongMessageInStore - ]; - store.dispatch(websocketActions.calculateLogMemory(exampleAllLogs)); - const expectedPayload = { - type: actionTypes.CALCULATE_LOG_MEMORY, - allLogs: exampleAllLogs - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); - - test("newLog should dispatch an action with the message in the payload", () => { - const store = mockStore({}); - store.dispatch(logActions.newLog(exampleShortMessage)); - const expectedPayload = { - type: actionTypes.NEW_LOG, - message: exampleShortMessage - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); - - test("removeOldestLog should dispatch an action with no payload", () => { - const store = mockStore({}); - store.dispatch(logActions.removeOldestLog()); - const expectedPayload = { - type: actionTypes.REMOVE_OLDEST_LOG - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); - - test("newError should dispatch an action with the error in the payload", () => { - const store = mockStore({}); - store.dispatch(websocketActions.newError("something went wrong")); - const expectedPayload = { - type: actionTypes.NEW_ERROR, - error: "something went wrong" - }; - expect(store.getActions()).toEqual([expectedPayload]); - }); - - // THUNKS - const deviceIdShortMessages = exampleShortMessageInStore.message.deviceId; - const deviceIdLongMessages = exampleLongMessageInStore.message.deviceId; - const store = mockStore( - fromJS({ - devices: { - byId: { - deviceIdShortMessages: createExampleSubWOLogs( - deviceIdShortMessages - ).set("logs", fromJS([exampleShortMessageInStore.id])), - deviceIdLongMessages: createExampleSubWOLogs( - deviceIdLongMessages - ).set("logs", fromJS([exampleLongMessageInStore.id])) - }, - allIds: [deviceIdShortMessages, deviceIdLongMessages] - }, - logs: { - byId: { - [exampleShortMessageInStore.id]: exampleShortMessageInStore, - [exampleLongMessageInStore.id]: exampleLongMessageInStore - }, - allIds: [exampleShortMessageInStore.id, exampleLongMessageInStore.id] - }, - logMemoryCalculation: { - totalThroughput: AVERAGE_MEMCALC_INTERVAL - 1 - } - }) - ); - test("handleNewLog should always dispatch a NEW_LOG action first", () => { - store.dispatch(websocketActions.handleNewLog(exampleShortMessage)); - const expectedPayload = { - type: actionTypes.NEW_LOG, - message: exampleShortMessage - }; - expect(store.getActions()[0]).toEqual(expectedPayload); - }); -}); diff --git a/developer-ui-frontend/src/actions/actionTypes.js b/developer-ui-frontend/src/actions/actionTypes.js index 27ef351..ae33106 100644 --- a/developer-ui-frontend/src/actions/actionTypes.js +++ b/developer-ui-frontend/src/actions/actionTypes.js @@ -45,7 +45,7 @@ export const ENABLED_CHANGED = "ENABLED_CHANGED"; export const CONFIGURING_GATEWAY = "CONFIGURING_GATEWAY"; export const CONFIGURED_GATEWAY = "CONFIGURED_GATEWAY"; export const CONFIGURING_GATEWAY_FAILED = "CONFIGURING_GATEWAY_FAILED "; -export const SETTED_VIA_PROPERTY = "SETTED_VIA_PROPERTY"; +export const VIA_PROPERTY_SET = "VIA_PROPERTY_SET"; export const SETTING_VIA_PROPERTY = "SETTING_VIA_PROPERTY"; // Credentials export const CREATING_CREDENTIAL = "CREATING_CREDENTIAL"; @@ -56,8 +56,13 @@ export const CREDENTIAL_DELETED = "CREDENTIAL_DELETED"; export const DELETING_CREDENTIAL_FAILED = "DELETING_CREDENTIAL_FAILED"; export const INIT_EMPTY_CREDENTIAL = "INIT_EMPTY_CREDENTIAL"; export const UPDATING_CRED_INFO = "UPDATING_CRED_INFO"; -export const UPDATED_CRED_INFO = "UPDATED_CRED_INFO"; export const UPDATING_CRED_INFO_FAILED = "UPDATING_CRED_INFO_FAILED"; +export const CHANGING_CRED_ENABLED = "CHANGING_CRED_ENABLED"; +export const ENABLED_CRED_CHANGED = "ENABLED_CRED_CHANGED"; +export const UPDATING_CRED_SECRETS = "UPDATING_CRED_SECRETS"; +export const UPDATED_CRED_SECRETS = "UPDATED_CRED_SECRETS"; +export const UPDATING_CRED_SECRETS_FAILED = "UPDATING_CRED_SECRETS_FAILED"; +export const CHANGING_CRED_SECRETS = "CHANGING_CRED_SECRETS"; // Secrets export const CREATING_SECRET = "CREATING_SECRET"; export const CREATING_SECRET_FAILED = "CREATING_SECRET_FAILED"; diff --git a/developer-ui-frontend/src/api/schemas.js b/developer-ui-frontend/src/api/schemas.js new file mode 100644 index 0000000..bd7e9da --- /dev/null +++ b/developer-ui-frontend/src/api/schemas.js @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import { schema, denormalize } from "normalizr"; +import { Map, OrderedMap } from "immutable"; +import { SHA1, SHA256, SHA512 } from "crypto-js"; +import Base64 from "crypto-js/enc-base64"; + +export const calculateSecretId = (secret, authId) => + `${authId}-${Base64.stringify(SHA1(JSON.stringify(secret)))}`; + +const getHashFunctionByName = name => { + switch (name) { + // case "sha-1": + // return plain => Base64.stringify(SHA1(plain)); + case "sha-256": + return plain => Base64.stringify(SHA256(plain)); + case "sha-512": + return plain => Base64.stringify(SHA512(plain)); + default: + return new Error("Unsupported Hash Function Name provided"); + } +}; + +export const hashSecret = data => { + let hashedSecret = {}; + const { hashMethod, password, salt, ...other } = data; + let pw = ""; + const hashFn = getHashFunctionByName(hashMethod); + if (salt) { + pw = hashFn(atob(salt) + password); + } else { + pw = hashFn(password); + } + hashedSecret = { + "hash-function": hashMethod, + "pwd-hash": pw, + ...other + }; + if (salt) { + hashedSecret.salt = salt; + } + return hashedSecret; +}; + +export const Secret = new schema.Entity( + "secrets", + {}, + { + idAttribute: (value, parent) => calculateSecretId(value, parent["auth-id"]), + processStrategy: (value, parent) => ({ + ...value, + secretId: calculateSecretId(value, parent["auth-id"]) + }) + } +); + +export const Credential = new schema.Entity( + "credentials", + { + secrets: [Secret] + }, + { + idAttribute: "auth-id", + processStrategy: value => { + const { "device-id": deviceId, ...reducedVal } = value; // eslint-disable-line + return reducedVal; + } + } +); + +export const Registration = new schema.Entity( + "devices", + {}, + { + idAttribute: "device-id", + processStrategy: value => { + const { "device-id": deviceId, ...registrationInfo } = value; + return { + deviceId, + registrationInfo: { ...registrationInfo } + }; + } + } +); + +/** + * The denormalize method of normalizr doesn't undo all the steps that were applied + * with the processingStrategy. This method fixes the missing transformations to + * get back to the original API format. + * + * @param {string} deviceId The Id of the device for which the credential is denormalized + * @param {string} authId The Id of the credential that is denormalized + * @param {Map} credentials The normalized, immutable Map of credentials (stored under "byId") + * @param {Map} secrets The normalized, immutable Map of secrets (stored under "byId") + */ +export const denormalizeCredential = ( + deviceId, + authId, + credentials, + secrets +) => { + const entities = Map({ + credentials, + secrets + }); + let denormalized = denormalize( + { credentials: [authId] }, + { credentials: [Credential] }, + entities + ).credentials[0]; + // Delete the secretIds (secretIds are only used inside Redux, the API + // does not treat secrets as seperate entities!) + if (denormalized.get("secrets")) { + denormalized = denormalized.update("secrets", secretDenormalized => + secretDenormalized.map( + s => (s && s.get("secretId") ? s.delete("secretId") : s) + ) + ); + } + + // Return it as OrderedMap, with the device-id (This way the device-id is always the first key) + return OrderedMap() + .set("device-id", deviceId) + .merge(denormalized); +}; diff --git a/developer-ui-frontend/src/components/MessagingLiveFeed/FilteredLoggingsView/FilterableLogTable/__tests__/FilterableLogTable.test.js b/developer-ui-frontend/src/components/MessagingLiveFeed/FilteredLoggingsView/FilterableLogTable/__tests__/FilterableLogTable.test.js deleted file mode 100644 index b2f661e..0000000 --- a/developer-ui-frontend/src/components/MessagingLiveFeed/FilteredLoggingsView/FilterableLogTable/__tests__/FilterableLogTable.test.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import React from "react"; -import { shallow } from "enzyme"; -import FilterableLogTable from "../FilterableLogTable"; - -describe("FilterableLogTable", () => { - const testProps = { - toggleDevicesPanel: jest.fn(), - devicesPanelExpanded: true - }; - it("renders correctly", () => { - const wrapper = shallow(); - expect(wrapper.length).toEqual(1); - }); - it("toggles the DevicePanel on an expand button click", () => { - const wrapper = shallow(); - wrapper.find("div.expandButton").simulate("click"); - expect(testProps.toggleDevicesPanel).toHaveBeenCalled(); - }); -}); diff --git a/developer-ui-frontend/src/components/MessagingLiveFeed/InitialSubscriptionsSelection/InitialSubscriptionsSelection.js b/developer-ui-frontend/src/components/MessagingLiveFeed/InitialSubscriptionsSelection/InitialSubscriptionsSelection.js index 6eec303..f537b80 100644 --- a/developer-ui-frontend/src/components/MessagingLiveFeed/InitialSubscriptionsSelection/InitialSubscriptionsSelection.js +++ b/developer-ui-frontend/src/components/MessagingLiveFeed/InitialSubscriptionsSelection/InitialSubscriptionsSelection.js @@ -121,7 +121,8 @@ export class InitialSubscriptionsSelectionWrapped extends React.Component { } id="subConfigForm-container" - className={this.state.done ? "done" : ""}> + className={this.state.done ? "done" : ""} + >
- - Start - +
+ + Start + +
); diff --git a/developer-ui-frontend/src/components/MessagingLiveFeed/__tests__/MessagingLiveFeed.test.js b/developer-ui-frontend/src/components/MessagingLiveFeed/__tests__/MessagingLiveFeed.test.js deleted file mode 100644 index 7593d8b..0000000 --- a/developer-ui-frontend/src/components/MessagingLiveFeed/__tests__/MessagingLiveFeed.test.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import React from "react"; -import { shallow } from "enzyme"; -import MessagingLiveFeed from "../MessagingLiveFeed"; -import { MessagingLiveFeedWrapped } from "../MessagingLiveFeed"; -import { Provider } from "react-redux"; -import configureStore from "redux-mock-store"; -import toJson from "enzyme-to-json"; - -const mockStore = configureStore([]); // No middlewares used in these tests - -describe("", () => { - const testProps = { - configuredSubscriptions: [], - useWebsockets: jest.fn(), - handleNewSub: jest.fn() - }; - - it("renders the connected part correctly", () => { - // Initialize mockstore with empty state - const initialState = {}; - const store = mockStore(initialState); - const wrapper = shallow( - - - - ); - expect(wrapper.find(MessagingLiveFeed).length).toEqual(1); - }); - it("renders the presentational part correctly", () => { - const wrapper = shallow(); - expect(wrapper.length).toEqual(1); - }); -}); diff --git a/developer-ui-frontend/src/components/Registrations/AddRegistrationButton.js b/developer-ui-frontend/src/components/Registrations/AddRegistrationButton.js index ab7f491..27757e5 100644 --- a/developer-ui-frontend/src/components/Registrations/AddRegistrationButton.js +++ b/developer-ui-frontend/src/components/Registrations/AddRegistrationButton.js @@ -43,9 +43,13 @@ class AddRegistrationButton extends React.Component { return ( diff --git a/developer-ui-frontend/src/components/Registrations/AddSecretButton.js b/developer-ui-frontend/src/components/Registrations/AddSecretButton.js new file mode 100644 index 0000000..4f145e2 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/AddSecretButton.js @@ -0,0 +1,65 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +/** + * This Module is intended to be a multi select button (a main button with smaller child buttons) + * that "fly out" by clicking on the main button. + * This is just a temporary version for the MVP, in which only the Password Credential type is + * supported. + */ + +import "styles/flyOutButton.scss"; +import React, { Fragment } from "react"; +import PropTypes from "prop-types"; +import HoverTooltip from "components/common/HoverTooltip"; +import { Link } from "react-router-dom"; +// SVG Imports +import AddBox from "images/addBox.svg"; + +const MAIN_BUTTON_DIAM = 50; +const M_X = 25; +const M_Y = 25; + +class AddSecretButton extends React.Component { + constructor(props) { + super(props); + } + + mainButtonStyles() { + return { + width: MAIN_BUTTON_DIAM, + height: MAIN_BUTTON_DIAM, + top: M_Y - MAIN_BUTTON_DIAM / 2, + left: M_X - MAIN_BUTTON_DIAM / 2 + }; + } + + render() { + const { authId, deviceId } = this.props; + const tooltipIdFirstReg = "first-reg"; + return ( + + +
+ +
+ +
+ ); + } +} + +AddSecretButton.propTypes = { + authId: PropTypes.string.isRequired, + deviceId: PropTypes.string.isRequired +}; + +export default AddSecretButton; diff --git a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/CredentialsInfoContent.js b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/CredentialsInfoContent.js index 92f6ffd..64d2dd7 100644 --- a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/CredentialsInfoContent.js +++ b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/CredentialsInfoContent.js @@ -12,8 +12,9 @@ import AddCredentialTab from "./container/AddCredentialTab"; // Redux import { connect } from "react-redux"; import { - selectAllCredentialsApiFormat, - selectUninitializedCredentials + selectDenormalizedCredentials, + selectUninitializedCredentials, + selectIsFetchingByDeviceId } from "reducers/selectors"; import { initializeEmptyCredential } from "actions/CredentialActions"; import { addCustomNotification } from "actions/globalActions"; @@ -134,12 +135,16 @@ class CredentialsInfoContentWrapped extends Component { ( + render={({ match }) => ( )} /> @@ -155,7 +160,7 @@ class CredentialsInfoContentWrapped extends Component { const mapStateToProps = (state, ownProps) => ({ credentials: ownProps.selectedDevice - ? selectAllCredentialsApiFormat(state, ownProps.selectedDevice) + ? selectDenormalizedCredentials(state, ownProps.selectedDevice) : null, uninitializedCreds: selectUninitializedCredentials(state) }); diff --git a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AccordionTab.js b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AccordionTab.js index 7f50e31..04f1b94 100644 --- a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AccordionTab.js +++ b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AccordionTab.js @@ -5,6 +5,7 @@ import React, { Fragment, Component } from "react"; import PropTypes from "prop-types"; import ImmutablePropTypes from "react-immutable-proptypes"; import { Route, Link, withRouter } from "react-router-dom"; +import axios from "axios"; // Child Components import CredentialAccordionTabDropdown from "./CredentialAccordionTabDropdown"; import AccordionSection, { @@ -16,16 +17,58 @@ import JsonView, { JsonReadOnlyView } from "components/common/JsonView"; import HoverTooltip from "components/common/HoverTooltip"; +import { + withAccordionContext, + FOOTER_HEIGHT +} from "components/common/Accordion/Accordion"; // Redux +import { updateCredentialInfo } from "actions/CredentialActions"; import { selectIsFetchingByAuthId } from "reducers/selectors"; import { connect } from "react-redux"; // SVG Imports import CredentialIcon from "images/pwCredentialIcon.svg"; import AddSecretIcon from "images/addPwSecretIcon.svg"; +let schema; + +const JsonEditorWithContext = withAccordionContext(JsonEditor); + class AccordionTabWrapped extends Component { constructor(props) { super(props); + this.handleEditorSave = this.handleEditorSave.bind(this); + this.redirectToReadOnly = this.redirectToReadOnly.bind(this); + } + + componentDidMount() { + this.getSchemaFromApiAsync(); + } + + getSchemaFromApiAsync() { + return axios + .get("https://docs.bosch-iot-hub.com/schema/credential.schema.json") + .then(({ data }) => { + schema = data; + }) + .catch(error => { + console.error(error); + }); + } + + handleEditorSave(newInfo) { + this.props + .updateCredentialInfo(newInfo) + .then(() => this.redirectToReadOnly(true)); + } + + redirectToReadOnly(withChangesSaved) { + const { history, match } = this.props; + history.push( + `/registrations/${this.props.selectedDevice}/credentials/${ + match.params.authId + }`, + { fromRaw: true, withChangesSaved } + ); } render() { @@ -34,7 +77,8 @@ class AccordionTabWrapped extends Component { credential, isFetching, selectedDevice, - toggleIsOpened + toggleIsOpened, + disabled } = this.props; const authId = credential.get("auth-id"); const tooltipIdAddSecret = "add-secret-tt"; @@ -47,18 +91,22 @@ class AccordionTabWrapped extends Component { toggleIsOpened(authId)} expanded={expanded} - className="accordion-tab"> + className="accordion-tab" + disabled={disabled} + > }> + icon={} + > {expanded && ( {credential.get("firstInitTime") && ( + )}/additionalSecrets`} + > + inEditingMode={match.params.credentialSubMenu === "raw"} + > - {/* Raw Edit Functionality is currently not implemented for Credentials */} - {}} - onSubmit={() => {}} + this.redirectToReadOnly(false)} + onSubmit={this.handleEditorSave} editorConfig={{ statusBar: false }} + schema={schema} dynamicHeight /> @@ -102,12 +150,15 @@ class AccordionTabWrapped extends Component { } const AccordionTab = withRouter( - connect((state, ownProps) => ({ - isFetching: selectIsFetchingByAuthId( - state, - ownProps.credential.get("auth-id") - ) - }))(AccordionTabWrapped) + connect( + (state, ownProps) => ({ + isFetching: selectIsFetchingByAuthId( + state, + ownProps.credential.get("auth-id") + ) + }), + { updateCredentialInfo } + )(AccordionTabWrapped) ); AccordionTabWrapped.propTypes = { @@ -117,7 +168,9 @@ AccordionTabWrapped.propTypes = { selectedDevice: PropTypes.string, idIsOpened: PropTypes.string, history: PropTypes.object.isRequired, - match: PropTypes.object.isRequired + match: PropTypes.object.isRequired, + updateCredentialInfo: PropTypes.func, + disabled: PropTypes.bool }; export default AccordionTab; diff --git a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AddCredentialTab.js b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AddCredentialTab.js index a13be44..5a7c5f8 100644 --- a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AddCredentialTab.js +++ b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/AddCredentialTab.js @@ -35,11 +35,12 @@ class AddCredentialTabWrapped extends Component { const { initializeEmptyCredential, selectedDevice } = this.props; const newAuthId = values.get("authId"); this.setState({ inAddingMode: false }); - return initializeEmptyCredential( - newAuthId, - "hashed-password", - selectedDevice - ); + return initializeEmptyCredential({ + "auth-id": newAuthId, + type: "hashed-password", + "device-id": selectedDevice, + secrets: [] + }); } autogenAuthId() { diff --git a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/CredentialAccordionTabDropdown.js b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/CredentialAccordionTabDropdown.js index 35839b6..d816d39 100644 --- a/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/CredentialAccordionTabDropdown.js +++ b/developer-ui-frontend/src/components/Registrations/MainContent/CredentialsEditor/container/CredentialAccordionTabDropdown.js @@ -18,7 +18,8 @@ import MoreIcon from "images/moreIcon.svg"; import DeleteCredIcon from "images/deletePwCredentialIcon.svg"; import DeletePwSecretIcon from "images/deletePwSecretIcon.svg"; import AddPwSecretIcon from "images/addPwSecretIcon.svg"; - +import CodeIcon from "images/codeIcon.svg"; +import EditIcon from "images/editIcon.svg"; class CredentialAccordionTabDropdownWrapped extends Component { constructor(props) { super(props); @@ -28,22 +29,12 @@ class CredentialAccordionTabDropdownWrapped extends Component { }; this.menuOptions = [ { - value: "Add Secret", - icon: , - route: `/registrations/${props.selectedDevice}/credentials/${ + value: "Edit Credential", + icon: , + route: `/registrations/${props.selectedDevice}/${ props.authId - }/additionalSecrets`, - disabledHoverTooltipId: "addSecretDisabled", - disabledText: "You can not have more than 10 secrets in a credential" - }, - { - value: "Delete Secret", - icon: , - route: `/registrations/${props.selectedDevice}/credentials/${ - props.authId - }/deleteSecrets`, - disabledHoverTooltipId: "deleteSecretDisabled", - disabledText: "You must have at least one secret in a credential" + }/editCredential`, + disabledHoverTooltipId: "editCredentialDisabled" }, { value: "Delete Credential", @@ -53,24 +44,33 @@ class CredentialAccordionTabDropdownWrapped extends Component { }/deleteCredential`, disabledHoverTooltipId: "deleteCredentialDisabled", disabledText: "There are no credentials for this device" + }, + { + value: "Edit Raw", + icon: , + route: `/registrations/${props.selectedDevice}/credentials/${ + props.authId + }/raw`, + disabledHoverTooltipId: "editRawDisabled", + disabledText: "There are no credentials for this device" } ]; this.toggleDropdownMenu = this.toggleDropdownMenu.bind(this); } getIsDisabled(category) { - const { numberOfSecrets, numberOfCredentials } = this.props; + const { numberOfCredentials } = this.props; let isDisabled = null; switch (category) { - case "Add Secret": - isDisabled = numberOfSecrets >= 10; - break; - case "Delete Secret": - isDisabled = numberOfSecrets <= 1; + case "Edit Credential": + isDisabled = false; break; case "Delete Credential": isDisabled = numberOfCredentials === 0; break; + case "Edit Raw": + isDisabled = numberOfCredentials === 0; + break; default: return new Error("Unknown dropdown category"); } @@ -97,7 +97,8 @@ class CredentialAccordionTabDropdownWrapped extends Component { + ancorId={menuBtnId} + > {this.menuOptions.map((option, index) => ( {} : e => e.preventDefault()}> + onClick={selectedDeviceId ? () => {} : e => e.preventDefault()} + className={!selectedDeviceId ? "disabled" : null}> Registration Info fetchCredential(selectedDeviceId) : e => e.preventDefault() - }> + } + className={credentialsTabLocked ? "disabled" : null}> Credentials diff --git a/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/AddRegistrationModalContainer.js b/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/AddRegistrationModalContainer.js index 13e29f3..f86bbc4 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/AddRegistrationModalContainer.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/AddRegistrationModalContainer.js @@ -173,8 +173,16 @@ class AddRegistrationModalContainer extends React.Component { ) : ( ); } diff --git a/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/ConfirmationView/ConfirmationView.js b/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/ConfirmationView/ConfirmationView.js index 1902673..8e7e3a2 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/ConfirmationView/ConfirmationView.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/AddRegistrationModal/ConfirmationView/ConfirmationView.js @@ -30,8 +30,6 @@ export default class ConfirmationView extends Component { } componentWillReceiveProps(nextProps) { - console.log(this.props.inConfirmationMode); - console.log(nextProps.inConfirmationMode); if ( (nextProps.inConfirmationMode && !this.props.inConfirmationMode) || !this.state.checkmarkAnimationFinished @@ -41,7 +39,6 @@ export default class ConfirmationView extends Component { } startAnimations() { - console.log("Cdm ConfirmationView, props: ", this.props); // set shiftingAnimationFinished to true and start the staggered motion setTimeout(() => this.setState({ checkmarkAnimationFinished: true }), 1500); setTimeout(() => this.setState({ shiftingAnimationFinished: true }), 1750); diff --git a/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModal.js b/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModal.js index 64f3ab1..6a0febf 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModal.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModal.js @@ -39,8 +39,8 @@ const AddSecretModal = ({
- -
)}
- + changeIsOpen(!isOpen)} diff --git a/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModalContainer.js b/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModalContainer.js index f0cd2c9..01b9848 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModalContainer.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AddSecretModalContainer.js @@ -6,6 +6,7 @@ import { connect } from "react-redux"; import { Redirect, withRouter } from "react-router-dom"; import { default as AddSecretModalInner } from "./AddSecretModal"; import PropTypes from "prop-types"; +import { hashSecret } from "api/schemas"; import { reduxForm, formValueSelector } from "redux-form/immutable"; import { createNewSecret, @@ -46,7 +47,6 @@ class AddSecretModalWrapped extends Component { } submit(values) { - console.log("hashAlgorithm " + values); const { authId, deviceId, @@ -64,30 +64,22 @@ class AddSecretModalWrapped extends Component { secretData["not-after"] = values.get("notAfter").format(); } if (values.get("salt")) { - secretData.salt = values.get("salt"); + secretData.salt = btoa(values.get("salt")); } const hashAlgorithm = values.get("hashAlgorithm"); if (hashAlgorithm) { secretData.hashMethod = hashAlgorithm; } + const hashedSecret = hashSecret(secretData); if (firstInitialization) { - this.props.createNewCredential( - authId, - credentialType, - deviceId, - null, - null, - values.get("secretType"), - secretData - ); + this.props.createNewCredential({ + "auth-id": authId, + type: credentialType, + "device-id": deviceId, + secrets: [hashedSecret] + }); } else { - this.props.createNewSecret( - deviceId, - authId, - values.get("secretType"), - null, - secretData - ); + this.props.createNewSecret(deviceId, authId, hashedSecret); } this.changeIsOpen(false); } diff --git a/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AdvancedSection.js b/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AdvancedSection.js index 563c89b..f06e0a2 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AdvancedSection.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/AddSecretModal/AdvancedSection.js @@ -37,7 +37,8 @@ class AdvancedSection extends Component { expanded={expanded} toggle={this.toggleSection} className="expand-link" - openToTop> + openToTop + > Advanced diff --git a/developer-ui-frontend/src/components/Registrations/Modals/DeleteCredentialModal/index.js b/developer-ui-frontend/src/components/Registrations/Modals/DeleteCredentialModal/index.js index b69742a..aea83b8 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/DeleteCredentialModal/index.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/DeleteCredentialModal/index.js @@ -48,7 +48,9 @@ class DeleteCredentialModalWrapped extends Component { ) : ( ); } diff --git a/developer-ui-frontend/src/components/Registrations/Modals/DeleteRegistrationModal/index.js b/developer-ui-frontend/src/components/Registrations/Modals/DeleteRegistrationModal/index.js index bb781f6..c0c7495 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/DeleteRegistrationModal/index.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/DeleteRegistrationModal/index.js @@ -21,12 +21,10 @@ class DeleteRegistrationModalWrapped extends Component { super(props); this.state = { footerOptionChecked: true, - footerOptionCheckedSecond: true, isOpen: true }; this.changeIsOpen = this.changeIsOpen.bind(this); this.onCheckboxClick = this.onCheckboxClick.bind(this); - this.onCheckboxClickSecond = this.onCheckboxClickSecond.bind(this); this.confirm = this.confirm.bind(this); } @@ -36,21 +34,17 @@ class DeleteRegistrationModalWrapped extends Component { })); } - onCheckboxClickSecond() { - this.setState(state => ({ - footerOptionCheckedSecond: !state.footerOptionCheckedSecond - })); - } - changeIsOpen(opened) { this.setState({ isOpen: opened }); } confirm() { - const { resetSelectedDevice, deleteReg, deviceId } = this.props; + const { resetSelectedDevice, deleteReg, deviceId, history } = this.props; this.changeIsOpen(false); const rememberedSelection = deviceId; - resetSelectedDevice(); // Clear selection + // Clear selection + resetSelectedDevice(); + history.push("/registrations"); if (this.state.footerOptionChecked) { this.props .deleteAllCredentialsOfDevice(rememberedSelection) @@ -58,13 +52,6 @@ class DeleteRegistrationModalWrapped extends Component { } else { deleteReg(rememberedSelection); } - if (this.state.footerOptionCheckedSecond) { - this.props - .deleteAllCredentialsOfDevice(rememberedSelection) - .then(() => deleteReg(rememberedSelection)); - } else { - deleteReg(rememberedSelection); - } } render() { @@ -119,6 +106,7 @@ DeleteRegistrationModalWrapped.propTypes = { deleteReg: PropTypes.func.isRequired, deleteAllCredentialsOfDevice: PropTypes.func.isRequired, match: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, resetSelectedDevice: PropTypes.func.isRequired }; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/DeleteSecretModal/index.js b/developer-ui-frontend/src/components/Registrations/Modals/DeleteSecretModal/index.js deleted file mode 100644 index f907e91..0000000 --- a/developer-ui-frontend/src/components/Registrations/Modals/DeleteSecretModal/index.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import React from "react"; -import PropTypes from "prop-types"; -import { reduxForm, Field } from "redux-form/immutable"; -import { - ConfigurationModal, - ConfigurationModalHeader, - ConfigurationModalFooter, - ConfigurationModalBody -} from "components/common/dialogModals"; -import { Redirect, withRouter } from "react-router-dom"; -import DeleteSecretLogo from "images/deletePwSecretIcon.svg"; -import { connect } from "react-redux"; -import { deleteSecret } from "actions/CredentialActions"; -import { selectSecretsByCredentialId } from "reducers/selectors"; -import { toJS } from "components/helpers/to-js"; -import Dropdown from "components/common/Dropdown"; -// TODO: Enable Certificates Option - -const validate = values => { - const errors = {}; - if (!values.get("secretSelect")) { - errors.password = "Required"; - } - return errors; -}; - -class DeleteSecretModalWrapped extends React.Component { - constructor(props) { - super(props); - this.state = { - isOpen: true - }; - } - - changeIsOpen(opened) { - this.setState({ isOpen: opened }); - } - - submit(values) { - const { deviceId, authId } = this.props; - this.props.deleteSecret(deviceId, authId, values.get("secretSelect")); - this.changeIsOpen(false); - } - - render() { - const { authId, handleSubmit, secrets, match } = this.props; - const { isOpen } = this.state; - const subjectTitle = "Delete a Secret from "; - return isOpen ? ( -
- - } - /> - - -
- - ({ - value: secret.secretId, - id: secret.secretId + index - }))} - /> -
-
- this.changeIsOpen(!isOpen)} - confirm={handleSubmit(this.submit.bind(this))} - /> -
-
- ) : ( - - ); - } -} - -let DeleteSecretModal = reduxForm({ - form: "deleteSecret", - enableReinitialize: true, - validate -})(DeleteSecretModalWrapped); -const mapStateToProps = (state, ownProps) => { - const secrets = selectSecretsByCredentialId(state, ownProps.authId); - return { - initialValues: { secretSelect: secrets.getIn([0, "secretId"]) }, - secrets - }; -}; -DeleteSecretModal = withRouter( - connect( - mapStateToProps, - { - deleteSecret - } - )(toJS(DeleteSecretModal)) -); - -DeleteSecretModalWrapped.propTypes = { - authId: PropTypes.string, - handleSubmit: PropTypes.func.isRequired, - deviceId: PropTypes.string, - secrets: PropTypes.array.isRequired, - deleteSecret: PropTypes.func.isRequired, - match: PropTypes.object.isRequired -}; - -export default DeleteSecretModal; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/AddSecretView/AddSecretAdvancedSection.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/AddSecretView/AddSecretAdvancedSection.js new file mode 100644 index 0000000..06e18c2 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/AddSecretView/AddSecretAdvancedSection.js @@ -0,0 +1,83 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { connect } from "react-redux"; +import { change } from "redux-form/immutable"; +import uuid from "uuid/v4"; +// Child Components +import ExpandLink from "components/common/ExpandLink"; +import { TextField } from "components/common/textInputs"; +import { DatePicker } from "components/common/textInputs"; + +class AddSecretAdvancedSection extends Component { + constructor(props) { + super(props); + this.state = { + expanded: false + }; + this.toggleSection = this.toggleSection.bind(this); + this.autogenSalt = this.autogenSalt.bind(this); + } + + toggleSection() { + this.setState(state => ({ expanded: !state.expanded })); + } + + autogenSalt() { + this.props.changeSaltVal(uuid().substring(0, 8)); + } + + render() { + const { expanded } = this.state; + return ( +
+ + Advanced + + + {expanded && ( +
+ +
+
+ + + +
+
+
+ )} +
+ ); + } +} + +AddSecretAdvancedSection.propTypes = { + changeSaltVal: PropTypes.func.isRequired +}; + +AddSecretAdvancedSection = connect( + null, + dispatch => ({ + changeSaltVal: val => dispatch(change("addNewSecret", "salt", val)) + }) +)(AddSecretAdvancedSection); + +export default AddSecretAdvancedSection; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/AddSecretView/AddSecretView.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/AddSecretView/AddSecretView.js new file mode 100644 index 0000000..2c109fb --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/AddSecretView/AddSecretView.js @@ -0,0 +1,142 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { connect } from "react-redux"; +import { reduxForm } from "redux-form/immutable"; +import { withRouter } from "react-router-dom"; +import { toJS } from "components/helpers/to-js"; +import { hashSecret } from "api/schemas"; +import { createNewSecret } from "actions/CredentialActions"; +// Child Components +import AddSecretAdvancedSection from "./AddSecretAdvancedSection"; +import { TextField } from "components/common/textInputs"; +import { FlatButton } from "components/common/buttons"; +import Dropdown from "components/common/Dropdown"; +import AddPwSecretIcon from "images/addPwSecretIcon.svg"; +import CancelIcon from "images/cancelIcon.svg"; +import "styles/credentialModal.scss"; + +class AddSecretViewWrapped extends Component { + constructor(props) { + super(props); + this.state = {}; + this.submit = this.submit.bind(this); + } + + submit(values) { + const secretData = { + password: values.get("password") + }; + if (values.get("notBefore")) { + secretData["not-before"] = values.get("notBefore").format(); + } + if (values.get("notAfter")) { + secretData["not-after"] = values.get("notAfter").format(); + } + if (values.get("salt")) { + secretData.salt = values.get("salt"); + } + const hashAlgorithm = values.get("hashAlgorithm"); + if (hashAlgorithm) { + secretData.hashMethod = hashAlgorithm; + } + this.props.createNewSecret( + this.props.deviceId, + this.props.authId, + hashSecret(secretData) + ); + this.props.toggleAddingMode(); + } + + render() { + const { authId, toggleAddingMode, handleSubmit } = this.props; + return ( +
+
+
+
+ + + + + +
+
+
+ + +
+
+ +
+
+ +
+ + Save + +
+
+
+
+ ); + } +} + +let AddSecretView = reduxForm({ + form: "addNewSecret", + enableReinitialize: true +})(AddSecretViewWrapped); + +const mapStateToProps = (state, ownProps) => { + return { + initialValues: { + secretType: ownProps.secretType, + hashAlgorithm: "sha-512" + } + }; +}; + +AddSecretView = withRouter( + connect( + mapStateToProps, + { + createNewSecret + } + )(toJS(AddSecretView)) +); + +AddSecretViewWrapped.propTypes = { + deviceId: PropTypes.string, + authId: PropTypes.string, + toggleAddingMode: PropTypes.func, + secretType: PropTypes.string, + handleSubmit: PropTypes.func.isRequired, + createNewSecret: PropTypes.func.isRequired +}; + +export default AddSecretView; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretAdvancedSection.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretAdvancedSection.js new file mode 100644 index 0000000..69547e3 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretAdvancedSection.js @@ -0,0 +1,87 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Component, Fragment } from "react"; +import PropTypes from "prop-types"; +// Child Components +import { DatePicker } from "components/common/textInputs"; +import "styles/credentialModal.scss"; + +class SecretAdvancedSection extends Component { + constructor(props) { + super(props); + this.state = { + expanded: true + }; + } + + toggleSection() { + this.setState(state => ({ expanded: !state.expanded })); + } + + render() { + const { notBefore, notAfter, salt, inEditingMode, secretId } = this.props; + const { expanded } = this.state; + const saltText = salt || ""; + return ( +
+

Advanced

+ {expanded && ( +
+
+
+ + {saltText} +
+
+
+ {inEditingMode ? ( + + +
+ +
+
+ +
+
+ ) : ( + +
+ + {notBefore} +
+
+ + {notAfter} +
+
+ )} +
+
+ )} +
+ ); + } +} + +SecretAdvancedSection.propTypes = { + authId: PropTypes.string, + secretId: PropTypes.string, + notBefore: PropTypes.string, + notAfter: PropTypes.string, + salt: PropTypes.string, + inEditingMode: PropTypes.bool +}; + +export default SecretAdvancedSection; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainView.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainView.js new file mode 100644 index 0000000..8fdab27 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainView.js @@ -0,0 +1,104 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React from "react"; +import PropTypes from "prop-types"; +import SecretMainViewBody from "./SecretMainViewBody"; +import { FlatButton } from "components/common/buttons"; +import { withContentRect } from "react-measure"; + +/* + This component uses CSS based transitions to animate the currently selected secret as + a card. The card is translated relative to its offset with the currently selected + card index (which is provided through the selectedSecret-prop). + The translation is applied through a x-axis translation and through a z-axis translation. +*/ + +const calcTranslation = (diffIndex, scaling) => { + if (diffIndex < 1) { + return -1 * scaling * Math.log2(Math.abs(diffIndex) + 1); + } + return scaling * Math.log2(diffIndex + 1); +}; + +const calcOpacity = (diffIndex, scaling) => { + if (diffIndex < 1) { + return -1 * (1 / scaling) * Math.abs(diffIndex) + 1; + } + return -1 * (1 / scaling) * diffIndex + 1; +}; + +const SecretMainView = ({ + secrets, + contentRect, + inEditingMode, + toggleEditingMode, + measureRef, + selectedSecret +}) => ( +
+ {secrets.map((secret, index) => ( +
+ + + {inEditingMode && ( +
+ + Save + +
+ )} +
+
+ ))} +
+); + +SecretMainView.propTypes = { + secrets: PropTypes.arrayOf( + PropTypes.shape({ + secretId: PropTypes.string.isRequired, + pwdHash: PropTypes.string, + "not-before": PropTypes.string, + "not-after": PropTypes.string, + salt: PropTypes.string + }) + ), + contentRect: PropTypes.object, + measureRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ current: PropTypes.instanceOf(Element) }) + ]), + selectedSecret: PropTypes.number, + inEditingMode: PropTypes.bool.isRequired, + toggleEditingMode: PropTypes.func.isRequired, + secretIndex: PropTypes.number +}; + +export default withContentRect("client")(SecretMainView); diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainViewBody.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainViewBody.js new file mode 100644 index 0000000..e1f3249 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainViewBody.js @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Fragment } from "react"; +import PropTypes from "prop-types"; +// Child Components +import SecretAdvancedSection from "./SecretAdvancedSection"; + +const SecretMainViewBody = ({ secret, inEditingMode }) => ( + +
+
+ {" "} + {secret["hash-function"]} +
+
+ {" "} + {secret["pwd-hash"]} +
+
+ +
+); + +SecretMainViewBody.propTypes = { + secret: PropTypes.shape({ + secretId: PropTypes.string.isRequired, + pwdHash: PropTypes.string, + "not-before": PropTypes.string, + "not-after": PropTypes.string, + salt: PropTypes.string + }), + inEditingMode: PropTypes.bool +}; + +export default SecretMainViewBody; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainViewHeader.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainViewHeader.js new file mode 100644 index 0000000..47e1928 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretMainView/SecretMainViewHeader.js @@ -0,0 +1,165 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Component, Fragment } from "react"; +import PropTypes from "prop-types"; +import { connect } from "react-redux"; +import { deleteSecret } from "actions/CredentialActions"; +import { selectSecretsByCredentialId } from "reducers/selectors"; +// Child Components +import { SimpleButton } from "components/common/buttons"; +import AddBox from "images/addBox.svg"; +import RemoveBox from "images/removeBox.svg"; +import EditButton from "images/editIcon.svg"; +import CaretDown from "images/caretDownIcon.svg"; +import "styles/credentialModal.scss"; + +class SecretMainViewHeaderWrapped extends Component { + constructor(props) { + super(props); + this.state = { + confirmDialogShown: false + }; + this.toggleConfirmDialog = this.toggleConfirmDialog.bind(this); + this.confirmRemove = this.confirmRemove.bind(this); + this.cancelDelete = this.cancelDelete.bind(this); + } + + toggleConfirmDialog() { + const { numberOfSecrets } = this.props; + if (numberOfSecrets > 1) { + this.setState({ confirmDialogShown: !this.state.confirmDialogShown }); + } + } + + confirmRemove() { + const { + deviceId, + authId, + numberOfSecrets, + selectedSecret, + selectedSecretId, + decreaseSelectedSecret + } = this.props; + this.props.deleteSecret(deviceId, authId, selectedSecretId); + if (selectedSecret === numberOfSecrets - 1) { + decreaseSelectedSecret(); + } + this.setState({ confirmDialogShown: !this.state.confirmDialogShown }); + } + + cancelDelete() { + this.setState({ confirmDialogShown: !this.state.confirmDialogShown }); + } + + render() { + const { + numberOfSecrets, + selectedSecret, + toggleAddingMode, + toggleEditingMode, + decreaseSelectedSecret, + inEditingMode, + increaseSelectedSecret + } = this.props; + const { confirmDialogShown } = this.state; + let renderedRightSideHeader = null; + if (confirmDialogShown) { + renderedRightSideHeader = ( +
+

Do you really want to delete the secret?

+ + Yes + + + No + +
+ ); + } else if (inEditingMode) { + renderedRightSideHeader = ( + + ); + } else { + renderedRightSideHeader = ( + + + + 1 ? "" : "disabled"}`} + /> + + ); + } + + return ( +
+
+ + + +
+
{renderedRightSideHeader}
+
+ ); + } +} + +const SecretMainViewHeader = connect( + (state, ownProps) => ({ + numberOfSecrets: selectSecretsByCredentialId(state, ownProps.authId).size + }), + { deleteSecret } +)(SecretMainViewHeaderWrapped); + +SecretMainViewHeaderWrapped.propTypes = { + deviceId: PropTypes.string, + authId: PropTypes.string, + inEditingMode: PropTypes.bool, + numberOfSecrets: PropTypes.number, + selectedSecret: PropTypes.number, + selectedSecretId: PropTypes.string, + deleteSecret: PropTypes.func, + toggleAddingMode: PropTypes.func, + toggleEditingMode: PropTypes.func, + decreaseSelectedSecret: PropTypes.func, + increaseSelectedSecret: PropTypes.func +}; + +export default SecretMainViewHeader; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretsConfigurator.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretsConfigurator.js new file mode 100644 index 0000000..24c827a --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/SecretsConfigurator/SecretsConfigurator.js @@ -0,0 +1,161 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { reduxForm } from "redux-form/immutable"; +import { connect } from "react-redux"; +import { toJS } from "components/helpers/to-js"; +import { selectSecretsByCredentialId } from "reducers/selectors"; +import { changeSecretsInfo } from "actions/CredentialActions"; +// Child Components +import SecretMainView from "./SecretMainView/SecretMainView"; +import SecretMainViewHeader from "./SecretMainView/SecretMainViewHeader"; +import AddSecretView from "./AddSecretView/AddSecretView"; +import "styles/credentialModal.scss"; + +class SecretsConfiguratorWrapped extends Component { + constructor(props) { + super(props); + this.state = { + selectedSecret: 0, + inAddingMode: false, + inEditingMode: false + }; + this.toggleEditingMode = this.toggleEditingMode.bind(this); + this.submit = this.submit.bind(this); + this.decreaseSelectedSecret = this.decreaseSelectedSecret.bind(this); + this.increaseSelectedSecret = this.increaseSelectedSecret.bind(this); + this.toggleAddingMode = this.toggleAddingMode.bind(this); + } + + toggleEditingMode() { + this.setState({ inEditingMode: !this.state.inEditingMode }); + } + + submit(values) { + this.setState({ inEditingMode: false }); + const currSecret = this.props.secrets[this.state.selectedSecret]; + if (values.get(currSecret["secretId"] + "notBefore")) { + currSecret["not-before"] = values + .get(currSecret["secretId"] + "notBefore") + .format(); + } + if (values.get(currSecret["secretId"] + "notBefore") === null) { + delete currSecret["not-before"]; + } + if (values.get(currSecret["secretId"] + "notAfter")) { + currSecret["not-after"] = values + .get(currSecret["secretId"] + "notAfter") + .format(); + } + if (values.get(currSecret["secretId"] + "notAfter") === null) { + delete currSecret["not-after"]; + } + this.props.changeSecretsInfo( + this.props.deviceId, + this.props.authId, + this.props.secrets + ); + this.setState({ edited: !this.state.edited }); + } + + decreaseSelectedSecret() { + this.setState(prevState => { + let newIndex = prevState.selectedSecret; + if (newIndex < 1) { + newIndex = 0; + } else { + newIndex = prevState.selectedSecret - 1; + } + return { selectedSecret: newIndex }; + }); + } + + increaseSelectedSecret() { + this.setState(prevState => { + let newIndex = prevState.selectedSecret; + const secretsLength = this.props.secrets.length - 1; + if (newIndex === secretsLength) { + newIndex = secretsLength; + } else { + newIndex = prevState.selectedSecret + 1; + } + return { selectedSecret: newIndex }; + }); + } + + toggleAddingMode() { + this.setState({ inAddingMode: !this.state.inAddingMode }); + } + + render() { + const { deviceId, authId, handleSubmit, secretType, secrets } = this.props; + const { selectedSecret, inAddingMode, inEditingMode } = this.state; + const selectedId = + secrets[selectedSecret] && secrets[selectedSecret].secretId; + return inAddingMode ? ( + + ) : ( +
+ + + + ); + } +} + +let SecretsConfigurator = reduxForm({ + form: "secretEditor", + enableReinitialize: true +})(SecretsConfiguratorWrapped); + +const mapStateToProps = (state, ownProps) => { + const secrets = selectSecretsByCredentialId(state, ownProps.authId); + return { + secrets + }; +}; + +const mapDispatchToProps = dispatch => ({ + changeSecretsInfo: (deviceId, authId, secrets) => + dispatch(changeSecretsInfo(deviceId, authId, secrets)) +}); + +SecretsConfigurator = connect( + mapStateToProps, + mapDispatchToProps +)(toJS(SecretsConfigurator)); + +SecretsConfiguratorWrapped.propTypes = { + deviceId: PropTypes.string, + authId: PropTypes.string, + secrets: PropTypes.array, + handleSubmit: PropTypes.func, + changeSecretsInfo: PropTypes.func, + secretType: PropTypes.string, + sercret: PropTypes.object +}; + +export default SecretsConfigurator; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/index.js b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/index.js new file mode 100644 index 0000000..53223d5 --- /dev/null +++ b/developer-ui-frontend/src/components/Registrations/Modals/EditCredentialModal/index.js @@ -0,0 +1,144 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Redirect } from "react-router-dom"; +import { connect } from "react-redux"; +import { toJS } from "components/helpers/to-js"; +import { withRouter } from "react-router-dom"; +import { + selectCredentialById, + selectSecretsByCredentialId +} from "reducers/selectors"; +import { changeEnabled } from "actions/CredentialActions"; +// Child Components +import { + ConfigurationModal, + ConfigurationModalHeader, + ConfigurationModalFooter, + ConfigurationModalBody +} from "components/common/dialogModals"; +import SecretsConfigurator from "./SecretsConfigurator/SecretsConfigurator"; +import SwitchCheckbox from "components/common/SwitchCheckbox"; +import SecretLogo from "images/pwCredentialIcon.svg"; +import "styles/credentialModal.scss"; + +class EditCredentialModalWrapped extends Component { + constructor(props) { + super(props); + this.state = { + isOpen: true + }; + this.changeIsOpen = this.changeIsOpen.bind(this); + this.toggleEnabled = this.toggleEnabled.bind(this); + } + + changeIsOpen(opened) { + this.setState({ isOpen: opened }); + } + + toggleEnabled() { + this.props.changeEnabled( + this.props.deviceId, + this.props.authId, + !this.props.enabled + ); + } + + render() { + const { authId, deviceId, secretType, enabled } = this.props; + const { isOpen } = this.state; + return isOpen ? ( + + + }> +
+ +
+
+ +
+
+ {" "} + {" " + authId} +
+
+ {" "} + {" " + deviceId} +
+
+ {" "} + {" " + secretType} +
+
+
+ +
+
+ +
+ ) : ( + + ); + } +} + +const mapStateToProps = (state, ownProps) => { + return { + enabled: Boolean( + selectCredentialById(state, ownProps.authId) && + selectCredentialById(state, ownProps.authId).get("enabled") + ), + secretType: + selectCredentialById(state, ownProps.authId) && + selectCredentialById(state, ownProps.authId).get("type"), + secrets: + selectSecretsByCredentialId(state, ownProps.authId) && + selectSecretsByCredentialId(state, ownProps.authId) + }; +}; + +const EditCredentialModal = connect( + mapStateToProps, + { changeEnabled } +)(toJS(EditCredentialModalWrapped)); + +EditCredentialModalWrapped.propTypes = { + authId: PropTypes.string, + deviceId: PropTypes.string, + secretType: PropTypes.string, + enabled: PropTypes.bool, + secrets: PropTypes.arrayOf( + PropTypes.shape({ + secretId: PropTypes.string.isRequired, + pwdHash: PropTypes.string, + "not-before": PropTypes.string, + "not-after": PropTypes.string, + salt: PropTypes.string + }) + ), + changeIsOpen: PropTypes.func, + changeEnabled: PropTypes.func.isRequired +}; + +export default EditCredentialModal; diff --git a/developer-ui-frontend/src/components/Registrations/Modals/index.js b/developer-ui-frontend/src/components/Registrations/Modals/index.js index b28efdb..57dbaaa 100644 --- a/developer-ui-frontend/src/components/Registrations/Modals/index.js +++ b/developer-ui-frontend/src/components/Registrations/Modals/index.js @@ -5,26 +5,50 @@ import React, { Fragment } from "react"; import PropTypes from "prop-types"; import { CREDENTIAL_TYPES } from "_APP_CONSTANTS"; import { Route } from "react-router-dom"; +import queryString from "query-string"; // Child Components import AddRegistrationModal from "./AddRegistrationModal"; import AddSecretModal from "./AddSecretModal"; import DeleteRegistrationModal from "./DeleteRegistrationModal"; import DeleteCredentialModal from "./DeleteCredentialModal"; -import DeleteSecretModal from "./DeleteSecretModal"; +/* eslint-disable react/no-multi-comp */ import AddGatewayModal from "./AddGatewayModal"; +import EditCredentialModal from "./EditCredentialModal"; const Modals = ({ setMainPanel }) => ( { + const queryParams = queryString.parse(location.search); + let renderedModal = null; + if (queryParams.action) { + switch (queryParams.action) { + case "additionalRegs": + renderedModal = ( + + ); + break; + default: + return null; + } + } + return renderedModal; + }} + /> + ( - )} /> @@ -35,38 +59,44 @@ const Modals = ({ setMainPanel }) => ( )} /> ( - )} /> ( - - )} + path={`/registrations/:selectedDeviceId/:registrationsSubMenu?/:authId?`} + render={({ location, match }) => { + const queryParams = queryString.parse(location.search); + let renderedModal = null; + if (queryParams.action) { + switch (queryParams.action) { + case "deleteReg": + renderedModal = ( + + ); + break; + default: + return null; + } + } + return renderedModal; + }} /> ( - )} /> - ( - - )} - /> ); diff --git a/developer-ui-frontend/src/components/Registrations/Registrations.js b/developer-ui-frontend/src/components/Registrations/Registrations.js index 898220d..7282871 100644 --- a/developer-ui-frontend/src/components/Registrations/Registrations.js +++ b/developer-ui-frontend/src/components/Registrations/Registrations.js @@ -10,7 +10,11 @@ import { withRouter, Route } from "react-router-dom"; import { matchPath } from "react-router"; // Redux import { connect } from "react-redux"; -import { selectNumberOfAllDevices, hasDevice } from "reducers/selectors"; +import { + selectNumberOfAllDevices, + hasDevice, + selectIsFetchingAny +} from "reducers/selectors"; import { formValueSelector, change } from "redux-form/immutable"; // Child Components import BigCard from "components/common/BigCard"; @@ -62,13 +66,14 @@ export class Registrations extends React.Component { render() { const { mainPanelExpanded } = this.state; - const { numberOfDevices } = this.props; + const { numberOfDevices, anyFetching } = this.props; return ( ( @@ -100,6 +105,7 @@ Registrations.propTypes = { initialState: PropTypes.object, numberOfDevices: PropTypes.number.isRequired, selectedDevice: PropTypes.string, + anyFetching: PropTypes.bool, history: PropTypes.object.isRequired, changeCurrentlySelectedDevice: PropTypes.func.isRequired, validateDeviceId: PropTypes.func.isRequired @@ -112,6 +118,7 @@ Registrations = withRouter( state, "selectedDevice" ), + anyFetching: selectIsFetchingAny(state), numberOfDevices: selectNumberOfAllDevices(state), validateDeviceId: deviceId => hasDevice(state, deviceId) }), diff --git a/developer-ui-frontend/src/components/common/Accordion/Accordion.js b/developer-ui-frontend/src/components/common/Accordion/Accordion.js index a90b719..3552ca3 100644 --- a/developer-ui-frontend/src/components/common/Accordion/Accordion.js +++ b/developer-ui-frontend/src/components/common/Accordion/Accordion.js @@ -7,7 +7,7 @@ import styled from "styled-components"; import Measure from "react-measure"; import throttle from "lodash.throttle"; -const FOOTER_HEIGHT = 40; +export const FOOTER_HEIGHT = 40; /* eslint-disable react/no-multi-comp */ // AccordionContainer constrains the dimensions if the maximum height gets exceeded @@ -42,12 +42,16 @@ const FixedFooterPortal = styled.div` // Create a context to expose state that is global to the whole componenent (asField, name, leadingCheckbox) // to all children -const AccordionContext = React.createContext(); +export const AccordionContext = React.createContext(); // Also create a HOC as simple API to wrap the children export const withAccordionContext = WrappedComponent => props => ( - {({ snapFooter }) => ( - + {({ snapFooter, containerHeight }) => ( + )} ); diff --git a/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSection.js b/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSection.js index 36650d5..d8d6993 100644 --- a/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSection.js +++ b/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSection.js @@ -17,6 +17,7 @@ AccordionSection.propTypes = { toggleExpanded: requiredIf(PropTypes.func, props => !props.sticky), sticky: PropTypes.bool, className: PropTypes.string, + disabled: PropTypes.bool, children: props => { if ( !Array.isArray(props.children) || diff --git a/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSectionHeader.js b/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSectionHeader.js index 014bc2d..41e29b9 100644 --- a/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSectionHeader.js +++ b/developer-ui-frontend/src/components/common/Accordion/AccordionSection/AccordionSectionHeader.js @@ -23,7 +23,7 @@ const ClickProxy = styled.span` const HeaderContainer = styled.div` position: relative; - z-index: 1; + z-index: 0; display: flex; justify-content: space-between; height: 4rem; @@ -37,6 +37,13 @@ const HeaderContainer = styled.div` ${props => props.expanded && `box-shadow: inset 0px -5px 5px 0px rgba(28, 33, 35, 0.14);`}; + ${props => + props.disabled && + ` + pointer-events: none; + background: #e4e4e4 !important; + color: rgba(33, 33, 33, 0.47) !important; + `} `; const ActionButtonsWrapper = styled.span` @@ -73,7 +80,12 @@ const TitleIcon = styled(({ icon, ...props }) => margin-right: 1rem; overflow: visible; path { - fill: ${props => props.theme.accentColor}; + fill: ${props => { + if (props.disabled) { + return "rgba(33, 33, 33, 0.47)"; + } + return props.theme.accentColor; + }}; } `; @@ -84,18 +96,19 @@ const AccordionSectionHeader = ({ toggleExpanded, children, sticky, + disabled, ...other }) => (
- + - {icon ? : null} + {icon ? : null} {title} {(expanded || sticky) && children} - {!sticky && ( + {!sticky && !disabled && ( ( +const infiniteProgress = keyframes` + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(400%); + } +`; + +const openAnimation = keyframes` + from { + transform: scaleY(0); + } to { + transform: scaleY(1); + } +`; + +const LoadingBar = styled.div` + position: absolute; + bottom: 0; + height: 0.3rem; + width: 100%; + background-color: rgba(255, 255, 255, 0.3); + animation: ${openAnimation} 0.5s cubic-bezier(0.12, 2, 0.67, 0.98) forwards; + + &:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 25%; + height: 100%; + transform: translateX(-100%); + background: linear-gradient( + to right, + ${props => transparentize(1, props.theme.accentGreen)} 0%, + ${props => rgba(props.theme.accentGreen, 1)} 10%, + ${props => rgba(props.theme.accentGreen, 1)} 90%, + ${props => transparentize(1, props.theme.accentGreen)} 100% + ); + z-index: 3; + margin: 0; + transform-origin: 50% 100%; + animation: ${infiniteProgress} 2s cubic-bezier(0.7, 0.01, 0.37, 1) infinite; + } +`; + +const BigCard = ({ title, children, loadingBarShown, ...other }) => ( - {title} + + {title} + {loadingBarShown && } + {children} ); @@ -75,7 +126,8 @@ BigCard.propTypes = { children: PropTypes.oneOfType([ PropTypes.node, PropTypes.arrayOf(PropTypes.node) - ]) + ]), + loadingBarShown: PropTypes.bool }; export default BigCard; diff --git a/developer-ui-frontend/src/components/common/Dropdown/Dropdown.js b/developer-ui-frontend/src/components/common/Dropdown/Dropdown.js index 205ade9..c076d93 100644 --- a/developer-ui-frontend/src/components/common/Dropdown/Dropdown.js +++ b/developer-ui-frontend/src/components/common/Dropdown/Dropdown.js @@ -2,6 +2,7 @@ * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. */ import React, { Component } from "react"; +import { Field } from "redux-form/immutable"; import PropTypes from "prop-types"; import styled from "styled-components"; import enhanceWithClickOutside from "react-click-outside"; @@ -29,6 +30,8 @@ const DropdownWrapper = styled.div` z-index: 4;`}; `; +/* eslint-disable react/no-multi-comp */ +/* eslint-disable react/prop-types */ class Dropdown extends Component { constructor(props) { super(props); @@ -50,10 +53,15 @@ class Dropdown extends Component { } selectItem(item) { + const { onChange } = this.props; this.setState({ selectedItem: item, showItems: false }); + /* If the component is a Redux Form Field, + the onChange is provided as prop and needs to be called + with the selection */ + onChange && onChange(item.value); } handleClickOutside() { @@ -61,10 +69,10 @@ class Dropdown extends Component { } render() { - const { input, items } = this.props; + const { items, ...other } = this.props; const { selectedItem, showItems } = this.state; return ( - + ( + +); + +export default props => + props.asField ? ( + + ) : ( + + ); diff --git a/developer-ui-frontend/src/components/common/Dropdown/DropdownMenu.js b/developer-ui-frontend/src/components/common/Dropdown/DropdownMenu.js index 433c2e9..2ed9c85 100644 --- a/developer-ui-frontend/src/components/common/Dropdown/DropdownMenu.js +++ b/developer-ui-frontend/src/components/common/Dropdown/DropdownMenu.js @@ -70,7 +70,10 @@ const DropdownMenu = ({ show, items, input, selectItem }) => ( DropdownMenu.propTypes = { show: PropTypes.bool, items: PropTypes.arrayOf( - PropTypes.shape({ id: PropTypes.number, value: PropTypes.string }) + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + value: PropTypes.string + }) ), input: PropTypes.object, selectItem: PropTypes.func.isRequired diff --git a/developer-ui-frontend/src/components/common/ExpandLink.js b/developer-ui-frontend/src/components/common/ExpandLink.js index 9e828aa..8ccf46f 100644 --- a/developer-ui-frontend/src/components/common/ExpandLink.js +++ b/developer-ui-frontend/src/components/common/ExpandLink.js @@ -23,11 +23,11 @@ const Caret = styled.span` props.openToTop ? `0 0.4em 0.4em 0.4em` : `0.4em 0.4em 0 0.4em`}; border-color: ${props => props.openToTop - ? `transparent transparent ${props.theme.accentBlue} + ? `transparent transparent ${props.theme.accentBlue} transparent` : `${props.theme.accentBlue} transparent transparent transparent`}; - margin-right: 1em; + margin-right: 0.5em; ${props => props.expanded && diff --git a/developer-ui-frontend/src/components/common/JsonEditor/__stories__/JsonEditor.stories.js b/developer-ui-frontend/src/components/common/JsonEditor/__stories__/JsonEditor.stories.js deleted file mode 100644 index 7c96b9e..0000000 --- a/developer-ui-frontend/src/components/common/JsonEditor/__stories__/JsonEditor.stories.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import React from "react"; -import "styles/app.scss"; -import "styles/stories.scss"; -import { storiesOf } from "@storybook/react"; -import { withInfo } from "@storybook/addon-info"; - -import JsonEditor from "../index"; - -storiesOf("Styleguide", module).add( - "JsonEditor", - withInfo({ propTables: [JsonEditor] })(() => ( -
-

Json Editor

-
- -
-
- )) -); diff --git a/developer-ui-frontend/src/components/common/JsonView/JsonEditor/JsonEditor.js b/developer-ui-frontend/src/components/common/JsonView/JsonEditor/JsonEditor.js index 4b7510d..b90058e 100644 --- a/developer-ui-frontend/src/components/common/JsonView/JsonEditor/JsonEditor.js +++ b/developer-ui-frontend/src/components/common/JsonView/JsonEditor/JsonEditor.js @@ -2,6 +2,7 @@ * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. */ import React, { Component } from "react"; +import ReactDOM from "react-dom"; import { JsonEditor as Editor } from "jsoneditor-react"; import styled from "styled-components"; import { withRouter } from "react-router-dom"; @@ -11,17 +12,20 @@ import PropTypes from "prop-types"; import ace from "brace"; import "styles/jsonEditor.scss"; import "brace/mode/json"; -// import "brace/theme/tomorrow_night_blue"; import "./customAceTheme"; +import Ajv from "ajv"; const FixedBtnsContainer = styled.div` position: absolute; right: 0; - bottom: 0; + ${props => (props.snapFooter ? `bottom: 100%` : `bottom: 0`)}; + ${props => (props.snapFooter ? `z-index: 0;` : `z-index: 5;`)}; justify-content: flex-end; align-items: flex-end; - margin: 1rem; - z-index: 1; + padding: 0.6rem; + background-color: #2d3e50; + border-top-left-radius: 1.5rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 2px 0 rgba(0, 0, 0, 0.24); `; const EditorBtn = styled(FlatButton)` @@ -33,6 +37,7 @@ const EditorBtn = styled(FlatButton)` const LINE_HEIGHT = 20; // px const BOTTOM_MARGIN = 10; // px +const ajv = new Ajv({ allErrors: false, verbose: false }); class JsonEditor extends Component { constructor(props) { @@ -70,21 +75,48 @@ class JsonEditor extends Component { style, className, dynamicHeight, - value + value, + schema, + snapFooter, + isLeaving } = this.props; const { currentJson } = this.state; + const renderedButtons = ( + + + CANCEL + + onSubmit(this.state.currentJson)}> + SAVE + + + ); + let fixedButtons; + // If the Accordion footer is snapped, render the buttons relative to the footer with a Portal + if (snapFooter) { + const footerPortalNode = document.getElementById( + "fixed-acc-footer-modal" + ); + if (footerPortalNode) { + fixedButtons = ReactDOM.createPortal(renderedButtons, footerPortalNode); + } + } else { + fixedButtons = renderedButtons; + } return (
- - location.state && location.state.withChangesSaved - ? true - : "Are you sure you want to quit the Editor? Changes will be discarded" - } - /> + {!isLeaving && ( + + location.state && location.state.withChangesSaved + ? true + : "Are you sure you want to quit the Editor? Changes will be discarded" + } + /> + )} - - - CANCEL - - onSubmit(this.state.currentJson)}> - SAVE - - + {!isLeaving && fixedButtons}
); } @@ -117,7 +144,10 @@ JsonEditor.propTypes = { style: PropTypes.object, className: PropTypes.string, dynamicHeight: PropTypes.bool, - location: PropTypes.object.isRequired + location: PropTypes.object.isRequired, + schema: PropTypes.object, + snapFooter: PropTypes.bool, + isLeaving: PropTypes.bool }; export default withRouter(JsonEditor); diff --git a/developer-ui-frontend/src/components/common/JsonView/JsonView.js b/developer-ui-frontend/src/components/common/JsonView/JsonView.js index acfc8b0..74bb4b8 100644 --- a/developer-ui-frontend/src/components/common/JsonView/JsonView.js +++ b/developer-ui-frontend/src/components/common/JsonView/JsonView.js @@ -24,7 +24,7 @@ class JsonView extends Component { const { value, inEditingMode, isFetching, children, location } = this.props; const childrenArr = React.Children.toArray(children); const JsonEditorChild = childrenArr.find( - child => child.type === JsonEditor + child => child.type !== JsonReadOnlyView ); const JsonReadOnlyChild = childrenArr.find( child => child.type === JsonReadOnlyView @@ -86,6 +86,7 @@ class JsonView extends Component { ? "two-digit-length" : "" }`, + isLeaving: editorTransition === "leaving", ...this.props }) )} @@ -113,23 +114,25 @@ JsonView.propTypes = { isFetching: PropTypes.bool, inEditingMode: PropTypes.bool, location: PropTypes.object.isRequired, + /* evntually passed through Accordion Context */ + snapFooter: PropTypes.bool, + containerHeight: PropTypes.number, children: (props, propName, componentName) => { const childs = props[propName]; const childTypes = childs.map(c => c.type); const expectedTypes = [JsonReadOnlyView, JsonEditor]; - const expectedTypesVariant = [JsonEditor, JsonReadOnlyView]; - // Only accept two children of the appropriate type + // Only accept two children of the appropriate type: + // Note: In some cases, the JsonEditor has to be wrapped inside + // a Context.Consumer -> Only check if there are 2 children and + // if one of them is of type JsonReadOnlyView. if ( React.Children.count(childs) !== 2 || - !( - childTypes.every((type, index) => type === expectedTypes[index]) || - childTypes.every((type, index) => type === expectedTypesVariant[index]) - ) + !childTypes.find((type, index) => type === JsonReadOnlyView) ) { return new Error( - `"${componentName}" should have two children of the following types: ${expectedTypes.join( - "`, `" - )}.` + `"${componentName}" should have two children of the following types: ${expectedTypes + .map(t => t.name) + .join("`, `")}.` ); } return null; diff --git a/developer-ui-frontend/src/components/common/SwitchCheckbox.js b/developer-ui-frontend/src/components/common/SwitchCheckbox.js index d3e3e5d..8b05d44 100644 --- a/developer-ui-frontend/src/components/common/SwitchCheckbox.js +++ b/developer-ui-frontend/src/components/common/SwitchCheckbox.js @@ -4,9 +4,20 @@ import React from "react"; import PropTypes from "prop-types"; -const SwitchCheckbox = ({ checked, onCheckboxClick, label }) => ( -
- +const SwitchCheckbox = ({ + checked, + onCheckboxClick, + label, + input, + className +}) => ( +
+
@@ -16,7 +27,9 @@ const SwitchCheckbox = ({ checked, onCheckboxClick, label }) => ( SwitchCheckbox.propTypes = { checked: PropTypes.bool.isRequired, onCheckboxClick: PropTypes.func.isRequired, - label: PropTypes.string + label: PropTypes.string, + input: PropTypes.object, + className: PropTypes.string }; export default SwitchCheckbox; diff --git a/developer-ui-frontend/src/components/common/buttons/SimpleButton.js b/developer-ui-frontend/src/components/common/buttons/SimpleButton.js new file mode 100644 index 0000000..b06dd35 --- /dev/null +++ b/developer-ui-frontend/src/components/common/buttons/SimpleButton.js @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. + */ +import React from "react"; +import PropTypes from "prop-types"; +import Button from "./Button"; +import { getThemeColor } from "utils"; + +const SimpleBtn = Button.extend` + -webkit-backface-visibility: hidden; + border: 0; + outline: none; + background: none; + opacity: 0.65; + transition: opacity 0.18s ease-out; + color: ${props => getThemeColor(props)}; + display: inline-flex; + align-items: center; + justify-content: center; + + a { + text-decoration: none; + color: inherit; + } + svg { + path { + ${props => + props.submitAnimation + ? `fill: ${props.theme.disabledFontColorHiContrast};` + : `fill: ${getThemeColor(props)};`}; + } + } + + &:hover { + opacity: 1; + ${props => + props.submitAnimation && + ` + svg { + transform: translateX(0.5em); + path { + fill: ${getThemeColor(props)}; + } + }`}; + } + &:disabled { + color: ${props => props.theme.disabledFontColorHiContrast}; + &:hover { + svg { + transform: translateX(0); + path { + transform: translateX(0); + fill: ${props => props.theme.disabledFontColorHiContrast}; + } + } + } + } +`; + +const SimpleButton = props => { + const { + children, + submitAnimation, + primary, + secondary, + danger, + ...other + } = props; + return ( + + {submitAnimation} + {children} + + ); +}; + +SimpleButton.propTypes = { + type: PropTypes.string, + children: PropTypes.string, + primary: PropTypes.bool, + secondary: PropTypes.bool, + danger: PropTypes.bool, + submitAnimation: PropTypes.bool +}; + +export default SimpleButton; diff --git a/developer-ui-frontend/src/components/common/buttons/index.js b/developer-ui-frontend/src/components/common/buttons/index.js index dfd59b4..60a28a8 100644 --- a/developer-ui-frontend/src/components/common/buttons/index.js +++ b/developer-ui-frontend/src/components/common/buttons/index.js @@ -4,3 +4,4 @@ export { default as OutlineButton } from "./OutlineButton"; export { default as FlatButton } from "./FlatButton"; export { default as RoundOutlineButton } from "./RoundOutlineButton"; +export { default as SimpleButton } from "./SimpleButton"; diff --git a/developer-ui-frontend/src/components/common/dialogModals/ConfigurationModal.js b/developer-ui-frontend/src/components/common/dialogModals/ConfigurationModal.js index 486c781..dbf8f1b 100644 --- a/developer-ui-frontend/src/components/common/dialogModals/ConfigurationModal.js +++ b/developer-ui-frontend/src/components/common/dialogModals/ConfigurationModal.js @@ -49,18 +49,24 @@ export const ConfigurationModalHeader = ({ icon, subject, subTitle, + children, ...other }) => (

{icon && icon} {subject} {subTitle && {subTitle}}

+ {children}
); ConfigurationModalHeader.propTypes = { subject: PropTypes.string.isRequired, subTitle: PropTypes.string, - icon: PropTypes.element + icon: PropTypes.element, + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node + ]) }; export const ConfigurationModalFooter = ({ @@ -77,9 +83,9 @@ export const ConfigurationModalFooter = ({ /> ); ConfigurationModalFooter.propTypes = { - submitType: PropTypes.oneOf(["delete", "submit"]).isRequired, + submitType: PropTypes.oneOf(["delete", "submit", "none"]).isRequired, toggleModal: PropTypes.func.isRequired, - confirm: PropTypes.func.isRequired, + confirm: PropTypes.func, submitBlocked: PropTypes.bool }; diff --git a/developer-ui-frontend/src/components/common/dialogModals/ConfirmationModal.js b/developer-ui-frontend/src/components/common/dialogModals/ConfirmationModal.js index 7503c8c..9ddbd5c 100644 --- a/developer-ui-frontend/src/components/common/dialogModals/ConfirmationModal.js +++ b/developer-ui-frontend/src/components/common/dialogModals/ConfirmationModal.js @@ -73,9 +73,9 @@ export const ConfirmationModalFooter = ({ }; ConfirmationModalFooter.propTypes = { - submitType: PropTypes.oneOf(["delete", "submit"]).isRequired, + submitType: PropTypes.oneOf(["delete", "submit"]), toggleModal: PropTypes.func.isRequired, - confirm: PropTypes.func.isRequired, + confirm: PropTypes.func, checkboxOption: PropTypes.shape({ checkboxLabel: PropTypes.string.isRequired, checked: PropTypes.bool.isRequired, @@ -156,7 +156,7 @@ ConfirmationModal.propTypes = { modalShown: PropTypes.bool.isRequired, subject: PropTypes.string, subTitle: PropTypes.string, - submitType: PropTypes.oneOf(["delete", "submit"]), + submitType: PropTypes.oneOf(["delete", "submit"]).isRequired, toggleModal: PropTypes.func, children: PropTypes.any, confirm: PropTypes.func, diff --git a/developer-ui-frontend/src/components/common/dialogModals/DialogModal.js b/developer-ui-frontend/src/components/common/dialogModals/DialogModal.js index bd97a44..e8b7a6f 100644 --- a/developer-ui-frontend/src/components/common/dialogModals/DialogModal.js +++ b/developer-ui-frontend/src/components/common/dialogModals/DialogModal.js @@ -53,7 +53,9 @@ export const DialogModalBase = styled(Modal)` `; export const DialogModalHeaderBase = styled.div` - display: inline-block; + display: inline-flex; + justify-content: space-between; + align-items: center; width: calc(100% - 2 * 2.4rem); white-space: nowrap; border-bottom: 1px solid rgba(34, 36, 38, 0.15); @@ -99,8 +101,7 @@ export const DialogModalFooter = ({ + onClick={confirm}> Delete ); @@ -111,29 +112,44 @@ export const DialogModalFooter = ({ disabled={Boolean(submitBlocked)} submitAnimation onClick={confirm} - type="submit" - > + type="submit"> Submit ); } - return ( - - {checkboxOption && checkboxOption} - - toggleModal(null, false)}> - Cancel - - {ConfirmBtn} - - - ); + let SecondButton = null; + if (submitType === "none") { + SecondButton = ( + + {checkboxOption && checkboxOption} + + toggleModal(null, false)}> + Close + + {ConfirmBtn} + + + ); + } else { + SecondButton = ( + + {checkboxOption && checkboxOption} + + toggleModal(null, false)}> + Cancel + + {ConfirmBtn} + + + ); + } + return SecondButton; }; DialogModalFooter.propTypes = { - submitType: PropTypes.oneOf(["delete", "submit"]).isRequired, + submitType: PropTypes.oneOf(["delete", "submit", "none"]).isRequired, toggleModal: PropTypes.func.isRequired, - confirm: PropTypes.func.isRequired, + confirm: PropTypes.func, submitBlocked: PropTypes.bool, /* If provided, this content gets added on the left hand side of the footer (usually a checkbox like "Don't show me again") */ diff --git a/developer-ui-frontend/src/components/common/textInputs/DatePicker.js b/developer-ui-frontend/src/components/common/textInputs/DatePicker.js index f7988e2..f5c2418 100644 --- a/developer-ui-frontend/src/components/common/textInputs/DatePicker.js +++ b/developer-ui-frontend/src/components/common/textInputs/DatePicker.js @@ -9,15 +9,20 @@ import { Field } from "redux-form/immutable"; import "styles/datePicker.scss"; import moment from "moment"; import CalendarIcon from "images/calendarIcon.svg"; +import CancelIcon from "images/cancelIcon.svg"; /* eslint-disable react/no-multi-comp */ class DatePickerWrapped extends Component { constructor(props) { super(props); + this.state = { + placeholderText: this.props.placeholder + }; this.handleChange = this.handleChange.bind(this); this.handleInputClick = this.handleInputClick.bind(this); this.handleBlur = this.handleBlur.bind(this); this.toggle = this.toggle.bind(this); + this.clearText = this.clearText.bind(this); this.component = React.createRef(); } @@ -30,7 +35,6 @@ class DatePickerWrapped extends Component { } handleChange(date) { - console.log(date); this.props.input.onChange(moment(date)); this.props.input.onBlur(); } @@ -47,25 +51,31 @@ class DatePickerWrapped extends Component { active ? this.props.input.onBlur() : this.props.input.onFocus(); } + clearText() { + this.props.input.onChange(null); + this.setState({ placeholderText: null }); + } + render() { const { - placeholder, input, meta: { active } } = this.props; - + const { placeholderText } = this.state; return (
{ this.component = r; }} - selected={input.value ? moment(input.value, "MM/DD/YYYY") : null} + dateFormat="YYYY-MM-DDTHH:mm:ss" + selected={input.value ? moment(input.value) : null} onChange={this.handleChange} onBlur={this.handleBlur} onClickOutside={this.handleBlur} - placeholderText={placeholder} + placeholderText={placeholderText} /> +
@@ -94,7 +104,8 @@ class DatePicker extends Component { this.state = { value: moment(), pristine: true, - active: false + active: false, + placeholder: "" }; this.handleChange = this.handleChange.bind(this); this.handleFocus = this.handleFocus.bind(this); diff --git a/developer-ui-frontend/src/images/addBox.svg b/developer-ui-frontend/src/images/addBox.svg new file mode 100644 index 0000000..8e517d8 --- /dev/null +++ b/developer-ui-frontend/src/images/addBox.svg @@ -0,0 +1 @@ + diff --git a/developer-ui-frontend/src/images/removeBox.svg b/developer-ui-frontend/src/images/removeBox.svg new file mode 100644 index 0000000..7471a56 --- /dev/null +++ b/developer-ui-frontend/src/images/removeBox.svg @@ -0,0 +1 @@ + diff --git a/developer-ui-frontend/src/images/saveIcon.svg b/developer-ui-frontend/src/images/saveIcon.svg index 8359ad7..a83612d 100644 --- a/developer-ui-frontend/src/images/saveIcon.svg +++ b/developer-ui-frontend/src/images/saveIcon.svg @@ -1,3 +1,3 @@ - + - \ No newline at end of file + diff --git a/developer-ui-frontend/src/reducers/ConnectionReducer.js b/developer-ui-frontend/src/reducers/ConnectionReducer.js index a48e59a..b059dd3 100644 --- a/developer-ui-frontend/src/reducers/ConnectionReducer.js +++ b/developer-ui-frontend/src/reducers/ConnectionReducer.js @@ -82,7 +82,7 @@ const connectionReducer = (state = initialState, action = {}) => { case actionTypes.CREATING_REG_FAILED: case actionTypes.NEW_REG: case actionTypes.REG_DELETED: - case actionTypes.SETTED_VIA_PROPERTY: + case actionTypes.VIA_PROPERTY_SET: case actionTypes.CONFIGURED_GATEWAY: case actionTypes.DELETING_REG_FAILED: case actionTypes.UPDATED_REG_INFO: @@ -102,6 +102,10 @@ const connectionReducer = (state = initialState, action = {}) => { case actionTypes.CREATING_CREDENTIAL: case actionTypes.DELETING_SECRET: case actionTypes.DELETING_CREDENTIAL: + case actionTypes.UPDATING_CRED_SECRETS: + case actionTypes.CHANGING_CRED_SECRETS: + case actionTypes.UPDATING_CRED_INFO: + case actionTypes.CHANGING_CRED_ENABLED: return state.updateIn(["fetchInProgress", "credentials", "byId"], ids => { const alreadyFetching = ids.some(id => id === action.authId); if (alreadyFetching) { @@ -118,6 +122,12 @@ const connectionReducer = (state = initialState, action = {}) => { case actionTypes.DELETING_SECRET_FAILED: case actionTypes.CREDENTIAL_DELETED: case actionTypes.DELETING_CREDENTIAL_FAILED: + case actionTypes.DELETING_CRED_INFO_FAILED: + case actionTypes.DELETING_CRED_SECRETS_FAILED: + case actionTypes.UPDATED_CRED_INFO: + case actionTypes.CHANGED_CRED_ENABLED: + case actionTypes.UPDATED_CRED_SECRETS: + case actionTypes.CHANGED_CRED_SECRETS: return state.updateIn(["fetchInProgress", "credentials", "byId"], ids => { const credIndex = ids.findIndex(id => id === action.authId); if (credIndex === -1) { diff --git a/developer-ui-frontend/src/reducers/CredentialsReducer.js b/developer-ui-frontend/src/reducers/CredentialsReducer.js index 1cc6958..c7f0035 100644 --- a/developer-ui-frontend/src/reducers/CredentialsReducer.js +++ b/developer-ui-frontend/src/reducers/CredentialsReducer.js @@ -7,11 +7,11 @@ import { NEW_SECRET, INIT_EMPTY_CREDENTIAL, SECRET_DELETED, + UPDATED_CRED_INFO, + UPDATED_CRED_SECRETS, CREDENTIAL_DELETED, CREDENTIALS_FETCHED, - UPDATED_CRED_INFO -} from "actions/actionTypes"; -import { calculateSecretId } from "utils"; + ENABLED_CRED_CHANGED} from "actions/actionTypes"; export const initialState = fromJS({ byId: {}, @@ -19,122 +19,102 @@ export const initialState = fromJS({ secrets: { byId: {}, allIds: [] } }); +const handleNewSecret = (state, action) => { + const newSecretId = action.secret.secretId; + return state.withMutations(reducedState => + reducedState + .updateIn(["byId", action.authId, "secrets"], ids => + ids.push(newSecretId) + ) + .updateIn(["secrets", "allIds"], ids => ids.push(newSecretId)) + .setIn(["secrets", "byId", newSecretId], fromJS(action.secret)) + ); +}; + const credentialsReducer = (state = initialState, action = {}) => { switch (action.type) { // CREDENTIALS_FETCHED means Credentials for a specific device-id are fetched case CREDENTIALS_FETCHED: { - // Loop over the payload and transfer each credential to the store format (defined in initialState) - // Update every credential (even those with auth ids that already exist in the store (the payload could be different)) - const newCredentials = state.toJS(); - action.data.credentials.forEach(cred => { - const { secrets, ...credWithoutSecrets } = cred; - const authId = cred["auth-id"]; - const credInStore = { - ...credWithoutSecrets, - secrets: [] - }; - // (1) Delete every secret associated with that auth-id and (2) adopt the secrets from the API response - if (newCredentials.byId[authId]) { - newCredentials.byId[authId].secrets.forEach(secretId => { - delete newCredentials.secrets.byId[secretId]; - newCredentials.secrets.allIds.splice( - newCredentials.secrets.allIds.findIndex(id => id === secretId), - 1 - ); - }); - } - secrets.forEach(secret => { - // (2) - const secretId = secret.secretId - ? secret.secretId - : calculateSecretId(secret, authId); - const secretInStore = { secretId, ...secret }; - newCredentials.secrets.allIds.push(secretId); - credInStore.secrets.push(secretId); - newCredentials.secrets.byId[secretId] = secretInStore; + if ( + action.data.entities && + action.data.entities.credentials && + action.data.entities.secrets && + action.data.result && + action.data.result.credentials + ) { + const newCredentials = fromJS({ + byId: action.data.entities.credentials, + allIds: action.data.result.credentials, + secrets: { + byId: action.data.entities.secrets, + allIds: Object.keys(action.data.entities.secrets) + } }); - // Either updates the credential or saves a new credential - newCredentials.byId[authId] = credInStore; - if (!newCredentials.allIds.some(id => id === authId)) { - newCredentials.allIds.push(authId); - } - }); - // Finally check if there are less credentials in the payload than stored for that device - // Which would mean the credential got deleted by another application in the meantime - action.prevAuthIds.forEach(credId => { - const stillAvailable = action.data.credentials.some( - cred => cred["auth-id"] === credId - ); - if (!stillAvailable) { - // Cleanup: Delete all associated secrets and the credential - const associatedSecretIds = newCredentials.byId[credId].secrets; - associatedSecretIds.forEach(secretId => { - delete newCredentials.secrets.byId[secretId]; - newCredentials.secrets.allIds.splice( - newCredentials.secrets.allIds.findIndex(id => id === secretId), - 1 - ); - }); - delete newCredentials.byId[credId]; - newCredentials.allIds.splice( - newCredentials.allIds.findIndex(id => id === credId), - 1 - ); - } - }); - return fromJS(newCredentials); + return newCredentials; + } + return state; } - case NEW_CREDENTIAL: + case NEW_CREDENTIAL: { + const newAuthId = action.credential["auth-id"]; + // secrets are handled by handleNewSecret -> Reset secrets to [] for + // credential specific handling + const { secrets, ...credentialWithoutSecrets } = action.credential; + credentialWithoutSecrets.secrets = []; return state.withMutations(reducedState => { const newState = reducedState .update( "allIds", ids => - reducedState.getIn(["byId", action.authId]) + reducedState.getIn(["byId", newAuthId]) ? ids - : ids.push(action.authId) + : ids.push(newAuthId) ) .setIn( - ["byId", action.authId], + ["byId", newAuthId], fromJS({ - "device-id": action.deviceId, enabled: true, - ...action.newCredential + ...credentialWithoutSecrets }) ) .updateIn( - ["byId", action.authId], + ["byId", newAuthId], cred => cred.get("firstInitTime") ? cred.delete("firstInitTime") : cred - ); + ) + .update(stateWithNewCreds => { + Object.keys(action.secrets).forEach(secret => { + stateWithNewCreds = handleNewSecret(stateWithNewCreds, { + authId: action.authId, + secret: { ...action.secrets[secret] } + }); + }); + }); return newState; }); - case INIT_EMPTY_CREDENTIAL: + } + case INIT_EMPTY_CREDENTIAL: { + const newAuthId = action.credential["auth-id"]; return state.withMutations(reducedState => - reducedState.update("allIds", ids => ids.push(action.authId)).setIn( - ["byId", action.authId], + reducedState.update("allIds", ids => ids.push(newAuthId)).setIn( + ["byId", newAuthId], fromJS({ - ...action.newCredential, + ...action.credential, firstInitTime: new Date().getTime() }) ) ); + } + case UPDATED_CRED_INFO: return state.withMutations(reducedState => reducedState .setIn(["byId", action.authId, "enabled"], action.enabled) .setIn(["byId", action.authId, "credentialInfo"], fromJS(action.data)) ); + case UPDATED_CRED_SECRETS: + return state.setIn(["secrets", "allIds"], fromJS(action.newSecrets)); case NEW_SECRET: - const newSecretId = action.secret.secretId; - return state.withMutations(reducedState => - reducedState - .updateIn(["secrets", "allIds"], ids => ids.push(newSecretId)) - .setIn(["secrets", "byId", newSecretId], fromJS(action.secret)) - .updateIn(["byId", action.authId, "secrets"], ids => - ids.push(newSecretId) - ) - ); + return handleNewSecret(state, action); case SECRET_DELETED: return state.withMutations(reducedState => reducedState @@ -146,14 +126,40 @@ const credentialsReducer = (state = initialState, action = {}) => { ids.filter(id => id !== action.secretId) ) ); + case ENABLED_CRED_CHANGED: + return state.setIn(["byId", action.authId, "enabled"], action.enabled); case CREDENTIAL_DELETED: - return state.withMutations(reducedState => - reducedState - .deleteIn(["byId", action.authId]) - .update("allIds", authIds => - authIds.filter(id => id !== action.authId) - ) - ); + // A Credential may not be loaded in the UI at the time it is deleted + // e.g. through "cascading delete" on its registration. This means, changes + // to the local app state are only needed if the credential is loaded + if (state.getIn(["byId", action.authId])) { + const deletedSecretIds = state.getIn([ + "byId", + action.authId, + "secrets" + ]); + return state.withMutations(reducedState => + reducedState + .deleteIn(["byId", action.authId]) + .update("allIds", authIds => + authIds.filter(id => id !== action.authId) + ) + .updateIn(["secrets", "byId"], secrets => { + let reducedSecrets = secrets; + deletedSecretIds.forEach(id => { + reducedSecrets = reducedSecrets.delete(id); + }); + return reducedSecrets; + }) + .updateIn(["secrets", "allIds"], secretIds => { + const reducedIds = secretIds.filter( + id => deletedSecretIds.indexOf(id) === -1 + ); + return reducedIds; + }) + ); + } + return state; default: return state; } diff --git a/developer-ui-frontend/src/reducers/DevicesReducer.js b/developer-ui-frontend/src/reducers/DevicesReducer.js index 0937a00..abd5d9e 100644 --- a/developer-ui-frontend/src/reducers/DevicesReducer.js +++ b/developer-ui-frontend/src/reducers/DevicesReducer.js @@ -3,32 +3,45 @@ */ import { fromJS } from "immutable"; import { calculateLogId, extractDeviceIdFromLog } from "utils"; -import { hubDevPresetRegistrations } from "__mocks__/storeMocks/deviceMocks"; -import { - NEW_LOG, - REMOVE_OLDEST_LOG, - NEW_SUB, - SUB_DELETED, - DELETING_SUB, - ADDING_SUB, - EVENTBUS_DISCONNECTED, - NEW_REG, - CONFIGURED_GATEWAY, - REGISTRATIONS_FETCHED, - REG_DELETED, - NEW_CREDENTIAL, - REMOVE_ALL_LOGS, - UPDATED_REG_INFO, - INIT_EMPTY_CREDENTIAL, - CREDENTIAL_DELETED, - CREDENTIALS_FETCHED -} from "actions/actionTypes"; +import * as actionTypes from "actions/actionTypes"; // Too many types for destructuring -> Create a namespace + +const createRegistration = issuer => { + const emptyReg = { + lastActive: null, + currentlyActive: false, + isSubscribed: false, + logs: [], + credentials: [] + }; + switch (issuer) { + case "fromApi": + return (deviceId, registrationInfo) => + fromJS( + Object.assign(emptyReg, { + deviceId, + configuredSubscribed: false, + registrationInfo + }) + ); + case "fromUi": + return (deviceId, configuredSubscribed) => + fromJS( + Object.assign(emptyReg, { + deviceId, + configuredSubscribed, + registrationInfo: { enabled: true } + }) + ); + default: + return new Error("Unknown issuer for createRegistration."); + } +}; export const initialState = fromJS({ byId: {}, allIds: [] }); const devicesReducer = (state = initialState, action = {}) => { switch (action.type) { - case NEW_LOG: + case actionTypes.NEW_LOG: const logId = calculateLogId(action.message); const deviceId = JSON.parse(action.message.body).deviceId; const time = action.message.timestamp; @@ -40,12 +53,12 @@ const devicesReducer = (state = initialState, action = {}) => { .setIn(["byId", deviceId, "lastActive"], time) .setIn(["byId", deviceId, "currentlyActive"], true); }); - case REMOVE_OLDEST_LOG: + case actionTypes.REMOVE_OLDEST_LOG: const oldestDeviceId = extractDeviceIdFromLog(action.id); return state.updateIn(["byId", oldestDeviceId, "logs"], loggings => loggings.filter(currId => currId !== action.id) ); - case REMOVE_ALL_LOGS: + case actionTypes.REMOVE_ALL_LOGS: return state.withMutations(reducedState => { let newState = reducedState; newState.get("allIds").forEach(id => { @@ -55,21 +68,21 @@ const devicesReducer = (state = initialState, action = {}) => { }); return newState; }); - case DELETING_SUB: + case actionTypes.DELETING_SUB: return state.setIn( ["byId", action.deviceId, "configuredSubscribed"], false ); - case SUB_DELETED: + case actionTypes.SUB_DELETED: return state.setIn(["byId", action.deviceId, "isSubscribed"], false); - case ADDING_SUB: + case actionTypes.ADDING_SUB: return state.setIn( ["byId", action.deviceId, "configuredSubscribed"], true ); - case NEW_SUB: + case actionTypes.NEW_SUB: return state.setIn(["byId", action.deviceId, "isSubscribed"], true); - case EVENTBUS_DISCONNECTED: + case actionTypes.EVENTBUS_DISCONNECTED: return state.withMutations(reducedState => { let newState = reducedState; newState.get("allIds").forEach(id => { @@ -81,69 +94,78 @@ const devicesReducer = (state = initialState, action = {}) => { }); return newState; }); - case REGISTRATIONS_FETCHED: { - let reduced = state; - const fetchedRegistrations = fromJS(action.registrations); - const newAllIds = fromJS(Object.keys(action.registrations)); - newAllIds.forEach(id => { - // Check if the device is already in the store (e.g. because of localStorage persistence) - if (state.getIn(["byId", id])) { - // If so, update just the registrationInfo - reduced = reduced.setIn( - ["byId", id, "registrationInfo"], - fetchedRegistrations.getIn([id, "registrationInfo"]) - ); - } else { - reduced = reduced.setIn(["byId", id], fetchedRegistrations.get(id)); - } - }); - // Delete all devices that are persisted but not fetched - if (newAllIds.size < state.get("allIds").size) { - const outdatedDevices = newAllIds - .filter(id => !state.get("allIds").includes(id)) - .concat(state.get("allIds").filter(id => !newAllIds.includes(id))); - outdatedDevices.forEach(id => { - reduced = reduced.deleteIn(["byId", id]); - }); + case actionTypes.REGISTRATIONS_FETCHED: { + if (action.data && action.data.result && action.data.entities) { + const fetched = fromJS(action.data); + const merged = fetched + .getIn(["entities", "devices"]) + .withMutations(fetchedDevices => { + fetched.getIn(["result", "devices"]).forEach(fetchedId => { + const oldReg = state.getIn(["byId", fetchedId]); + if (oldReg) { + // In Case of a conflict (e.g. always on the registrationInfo Prop), use the fetched version + /* eslint-disable no-unused-vars */ + fetchedDevices.update(fetchedId, dev => + dev.mergeWith((fetchedProp, oldProp) => fetchedProp, oldReg) + ); + /* eslint-enable no-unused-vars */ + } else { + fetchedDevices.update(fetchedId, dev => + createRegistration("fromApi")( + fetchedId, + dev.get("registrationInfo").toJS() + ) + ); + } + }); + }); + return state + .set("byId", merged) + .set("allIds", fromJS(action.data.result.devices)); } - reduced = reduced.set("allIds", newAllIds); - - return reduced; + return state; } - case CREDENTIALS_FETCHED: { - const fetchedCredentials = action.data.credentials; - const newAllIds = fromJS(fetchedCredentials.map(cred => cred["auth-id"])); - return state.setIn(["byId", action.deviceId, "credentials"], newAllIds); + case actionTypes.CREDENTIALS_FETCHED: { + if (action.data.result && action.data.result.credentials) { + return state.setIn( + ["byId", action.deviceId, "credentials"], + fromJS(action.data.result.credentials) + ); + } + return state; } - case NEW_REG: - const newReg = fromJS(action.device); + case actionTypes.NEW_REG: + const newReg = createRegistration("fromUi")( + action.deviceId, + action.configuredSubscribed + ); return state.withMutations(reducedState => reducedState - .setIn(["byId", action.device.deviceId], newReg) + .setIn(["byId", action.deviceId], newReg) .update("allIds", ids => ids.push(newReg.get("deviceId")).sort()) ); - case CONFIGURED_GATEWAY: { + case actionTypes.CONFIGURED_GATEWAY: { const gatewayProperty = action.info; return state.setIn( ["byId", action.deviceId, "registrationInfo", "via"], gatewayProperty.get("via") ); } - case REG_DELETED: + case actionTypes.REG_DELETED: return state.withMutations(reducedState => reducedState .deleteIn(["byId", action.deviceId]) .update("allIds", ids => ids.filter(id => id !== action.deviceId)) ); - case UPDATED_REG_INFO: + case actionTypes.UPDATED_REG_INFO: return state.setIn( ["byId", action.deviceId, "registrationInfo"], fromJS(action.info) ); - case NEW_CREDENTIAL: - case INIT_EMPTY_CREDENTIAL: + case actionTypes.NEW_CREDENTIAL: + case actionTypes.INIT_EMPTY_CREDENTIAL: const newCredentialDeviceId = action.deviceId; - const authId = action.authId; + const authId = action.credential["auth-id"]; return state.updateIn( ["byId", newCredentialDeviceId, "credentials"], credentials => @@ -151,7 +173,7 @@ const devicesReducer = (state = initialState, action = {}) => { ? credentials : credentials.push(authId) ); - case CREDENTIAL_DELETED: + case actionTypes.CREDENTIAL_DELETED: return state.updateIn(["byId", action.deviceId, "credentials"], creds => creds.filter(id => id !== action.authId) ); diff --git a/developer-ui-frontend/src/reducers/NotificationReducer.js b/developer-ui-frontend/src/reducers/NotificationReducer.js index 0b998a1..11ea288 100644 --- a/developer-ui-frontend/src/reducers/NotificationReducer.js +++ b/developer-ui-frontend/src/reducers/NotificationReducer.js @@ -51,7 +51,7 @@ const notificationReducer = (state = initialState, action = {}) => { case actionTypes.NEW_REG: return state.withMutations(reducedState => reducedState - .set("message", `${action.device.deviceId} successfully registered`) + .set("message", `${action.deviceId} successfully registered`) .set("level", "success") ); case actionTypes.CREATING_REG_FAILED: diff --git a/developer-ui-frontend/src/reducers/__tests__/ConnectionReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/ConnectionReducer.test.js deleted file mode 100644 index 40f6db6..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/ConnectionReducer.test.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import connectionReducer, { initialState } from "../ConnectionReducer"; -import { fromJS } from "immutable"; -import { CONNECT_TO_EVENTBUS } from "actions/actionTypes"; -import { - exampleConnectedAction, - exampleDisconnectedAction -} from "__mocks__/actionMocks"; -import { exampleEventBus } from "__mocks__/storeMocks/stateMocks"; - -describe("ConnectionReducer", () => { - it("should have initial state on empty call", () => { - expect(connectionReducer()).toEqual(initialState); - }); - - it("should not change state on unknown action types", () => { - expect(connectionReducer(undefined, { type: "NOT_EXISTING" })).toEqual( - initialState - ); - }); - - test("> CONNECT_TO_EVENTBUS switches eventBusConnected to false", () => { - expect( - connectionReducer(initialState.set("eventBusConnected", true), { - type: CONNECT_TO_EVENTBUS - }) - ).toEqual(initialState); - }); - - test("> CONNECT_TO_EVENTBUS does not change eventBus when eventBus is already false (initial)", () => { - expect( - connectionReducer(initialState, { type: CONNECT_TO_EVENTBUS }) - ).toEqual(initialState); - }); - - const expectedConnectionReturn = fromJS({ - eventBus: exampleEventBus, - eventBusConnected: true, - hubConnected: false, - fetchInProgress: { - tenant: false, - registrations: { - global: false, - byId: [] - }, - credentials: { - global: false, - byId: [] - } - } - }); - - test("> EVENTBUS_CONNECTED switches eventBusConnected to true", () => { - expect(connectionReducer(initialState, exampleConnectedAction)).toEqual( - expectedConnectionReturn - ); - }); - - test("> EVENTBUS_CONNECTED does not change eventBusConnected when connected is already true", () => { - expect( - connectionReducer(expectedConnectionReturn, exampleConnectedAction) - ).toEqual(expectedConnectionReturn); - }); - - const expectedDisconnectReturn = fromJS({ - eventBus: exampleEventBus, - eventBusConnected: false, - hubConnected: false, - fetchInProgress: { - tenant: false, - registrations: { - global: false, - byId: [] - }, - credentials: { - global: false, - byId: [] - } - } - }); - - test("> EVENTBUS_DISCONNECTED switches eventBusConnected to false", () => { - expect( - connectionReducer( - initialState.set("eventBusConnected", true), - exampleDisconnectedAction - ) - ).toEqual(expectedDisconnectReturn); - }); - - test("> EVENTBUS_DISCONNECTED does not change eventBusConnected when connected is already false", () => { - expect( - connectionReducer(expectedDisconnectReturn, exampleDisconnectedAction) - ).toEqual(expectedDisconnectReturn); - }); -}); diff --git a/developer-ui-frontend/src/reducers/__tests__/CredentialsReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/CredentialsReducer.test.js deleted file mode 100644 index a12cc2f..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/CredentialsReducer.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import credentialsReducer, { initialState } from "../CredentialsReducer"; -import { - exampleCredentialsFetchedAction, - exampleCredentialsFetchedAction2, - exampleCredentialsFetchedAction3, - exampleNewCredentialAction -} from "__mocks__/actionMocks"; -import { - credentialsAfterFirstGet, - credentialsAfterDeletedCred, - credentialsAfterNewCred -} from "__mocks__/storeMocks/credentialMocks"; - -describe("CredentialsReducer", () => { - it("should have initial state on empty call", () => { - expect(credentialsReducer()).toEqual(initialState); - }); - - it("should not change state on unknown action types", () => { - expect(credentialsReducer(undefined, { type: "NOT_EXISTING" })).toEqual( - initialState - ); - }); - - test('> CREDENTIALS_FETCHED with "empty store" (no credentials yet) saves every credential', () => { - expect( - credentialsReducer(initialState, exampleCredentialsFetchedAction) - ).toEqual(credentialsAfterFirstGet); - }); - - test("> CREDENTIALS_FETCHED with nothing new doesn't change state", () => { - expect( - credentialsReducer( - credentialsAfterFirstGet, - exampleCredentialsFetchedAction2 - ) - ).toEqual(credentialsAfterFirstGet); - }); - - test("> CREDENTIALS_FETCHED with less credentials than saved deletes missing credentials", () => { - expect( - credentialsReducer( - credentialsAfterFirstGet, - exampleCredentialsFetchedAction3 - ) - ).toEqual(credentialsAfterDeletedCred); - }); - - test("> NEW_CREDENTIAL should add the new auth-id to credentials->allIds", () => { - expect( - credentialsReducer(initialState, exampleNewCredentialAction).get("allIds") - ).toEqual(credentialsAfterNewCred.get("allIds")); - }); - - test("> NEW_CREDENTIAL should add the credential to credentials->byId", () => { - expect( - credentialsReducer(initialState, exampleNewCredentialAction).get("byId") - ).toEqual(credentialsAfterNewCred.get("byId")); - }); -}); diff --git a/developer-ui-frontend/src/reducers/__tests__/DevicesReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/DevicesReducer.test.js deleted file mode 100644 index 324c1db..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/DevicesReducer.test.js +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import { fromJS } from "immutable"; -import { calculateLogId } from "utils"; -import devicesReducer, { initialState } from "../DevicesReducer"; // initialState is already an Immutable Map -import { - exampleNewLogAction, - exampleRemoveOldestLogAction, - exampleDeleteSubAction, - exampleAddSubAction, - exampleDeletingSubAction, - exampleAddingSubAction -} from "__mocks__/actionMocks"; -import { - exampleShortMessageInStore, - exampleLongMessageInStore -} from "__mocks__/storeMocks/messageMocks"; -import { - hubDevPresetRegistrations as exampleInitialState, - createExampleSubWOLogs, - createExampleReg -} from "__mocks__/storeMocks/deviceMocks"; - -describe("DevicesReducer", () => { - it("should have initial state on empty call", () => { - expect(devicesReducer()).toEqual(initialState); - }); - - it("should not change state on unknown action types", () => { - expect(devicesReducer(undefined, { type: "NOT_EXISTING" })).toEqual( - initialState - ); - }); - - test("> NEW_LOG should push a new logId to the logs array of the corresponding device", () => { - const initialStateWithSub = exampleInitialState.setIn( - ["byId", "4746", "isSubscribed"], - true - ); - const expectedLogsReturn = fromJS([ - calculateLogId(exampleNewLogAction.message) - ]); - const result = devicesReducer(initialStateWithSub, exampleNewLogAction); - expect(result.getIn(["byId", "4746", "logs"])).toEqual(expectedLogsReturn); - }); - - test("> NEW_LOG should make the corresponding subscribed device currentlyActive", () => { - const result = devicesReducer(exampleInitialState, exampleNewLogAction); - expect(result.getIn(["byId", "4746", "currentlyActive"])).toBeTruthy(); - }); - - test("> REMOVE_OLDEST_LOG should delete the the corresponding logId from the logs array of the corresponding device", () => { - const initialStateWithSubsAndLogs = exampleInitialState - .setIn(["byId", "4746", "isSubscribed"], true) - .setIn(["byId", "HubTester", "isSubscribed"], true) - .setIn(["byId", "4746", "logs"], fromJS([exampleShortMessageInStore.id])) - .setIn( - ["byId", "HubTester", "logs"], - fromJS([exampleLongMessageInStore.id]) - ); - - const result = devicesReducer( - initialStateWithSubsAndLogs, - exampleRemoveOldestLogAction // Should remove the log from 4746 (deviceId of exampleShortMessage) - ); - expect(result.getIn(["byId", "4746", "logs"]).size).toEqual(0); - }); - - const initialStateWithSubs = fromJS({ - byId: { - 0: createExampleReg("0"), - 1: createExampleSubWOLogs("1"), - 2: createExampleSubWOLogs("2") - }, - allIds: ["0", "1", "2"] - }); - - test("> DELETING_SUB should set configuredSubscribed in the device with the corresponding id to false", () => { - const result = devicesReducer( - initialStateWithSubs, - exampleDeletingSubAction - ); - expect(result.getIn(["byId", "1", "configuredSubscribed"])).toBeFalsy(); - }); - - test("> SUB_DELETED should set isSubscribed in the device with the corresponding id to false", () => { - const result = devicesReducer( - initialStateWithSubs, - exampleDeleteSubAction // Should delete sub with deviceId '1' - ); - expect(result.getIn(["byId", "1", "isSubscribed"])).toBeFalsy(); - }); - - test("> ADDING_SUB should set configuredSubscribed in the device with the corresponding id to true", () => { - const result = devicesReducer(initialStateWithSubs, exampleAddingSubAction); - expect(result.getIn(["byId", "0", "configuredSubscribed"])).toBeTruthy(); - }); - - test("> NEW_SUB should set isSubscribed in the device with the corresponding id to true", () => { - const result = devicesReducer(initialStateWithSubs, exampleAddSubAction); // Should add sub with deviceId '0' - expect(result.getIn(["byId", "0", "isSubscribed"])).toBeTruthy(); - }); -}); diff --git a/developer-ui-frontend/src/reducers/__tests__/FilterReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/FilterReducer.test.js deleted file mode 100644 index 6a889df..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/FilterReducer.test.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import filterReducer, { initialState } from '../FilterReducer'; -import { - exampleNewFilterActionType, - exampleNewFilterActionDevice, - exampleNewFilterActionContent, - exampleRemoveFilterActionDevice, - exampleRemoveFilterActionContent -} from '__mocks__/actionMocks'; -import { - exampleTypeFilterInStore, - exampleDeviceFilterInStore, - exampleContentFilterInStore -} from '__mocks__/storeMocks/filterMocks'; -import { fromJS } from 'immutable'; - -describe('FilterReducer', () => { - const exampleContentFilterId = exampleContentFilterInStore.get('id'); - const exampleDeviceFilterId = exampleDeviceFilterInStore.get('id'); - const initialStateWithContentFilter = initialState - .set( - 'byId', - fromJS({ [exampleContentFilterId]: exampleContentFilterInStore }) - ) - .set('allIds', fromJS([exampleContentFilterId])); - const initialStateWithDeviceFilter = initialState - .set( - 'byId', - fromJS({ [exampleDeviceFilterId]: exampleDeviceFilterInStore }) - ) - .set('allIds', fromJS([exampleDeviceFilterId])); - - it('should have initial state on empty call', () => { - expect(filterReducer()).toEqual(initialState); - }); - - it('should not change state on unknown action types', () => { - expect(filterReducer(undefined, { type: 'NOT_EXISTING' })).toEqual( - initialState - ); - }); - - test('> NEW_FILTER should add a filter object with type and value properties to filters in byId', () => { - expect( - filterReducer(initialState, exampleNewFilterActionType).get('byId') - ).toEqual( - fromJS({ [exampleTypeFilterInStore.get('id')]: exampleTypeFilterInStore }) - ); - }); - - test('> NEW_FILTER should add a filterId to filters in allIds', () => { - expect( - filterReducer(initialState, exampleNewFilterActionType).get('allIds') - ).toEqual(fromJS([exampleTypeFilterInStore.get('id')])); - }); - - test('> NEW_FILTER should not add an already existing filter', () => { - expect( - filterReducer( - initialStateWithContentFilter, - exampleNewFilterActionContent - ) - ).toEqual(initialStateWithContentFilter); - }); - - test('> REMOVE_FILTER should remove the given filterId entry from allIds', () => { - const expectedReturn = fromJS([]); // Last filter gets removed - console.log('before: ', initialStateWithContentFilter.toJS()); - console.log( - 'after: ', - filterReducer( - initialStateWithContentFilter, - exampleRemoveFilterActionContent - ) - ); - expect( - filterReducer( - initialStateWithContentFilter, - exampleRemoveFilterActionContent - ).get('allIds') - ).toEqual(expectedReturn); - }); - - test('> REMOVE_FILTER should remove the corresponding filter entry from byId', () => { - const expectedReturn = fromJS({}); // Last filter gets removed - expect( - filterReducer( - initialStateWithDeviceFilter, - exampleRemoveFilterActionDevice - ).get('byId') - ).toEqual(expectedReturn); - }); -}); diff --git a/developer-ui-frontend/src/reducers/__tests__/LogMemoryCalculationReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/LogMemoryCalculationReducer.test.js deleted file mode 100644 index 6334213..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/LogMemoryCalculationReducer.test.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import logMemoryCalculation, { - calculateAverageLogSize, - roughSizeOfObject, - initialSettings as initialState -} from "../LogMemoryCalculationReducer"; -import { exampleCalculateLogMemoryAction } from "__mocks__/actionMocks"; -import { - exampleLongMessageInStore, - exampleShortMessageInStore -} from "__mocks__/storeMocks/messageMocks"; -import { AVERAGE_MEMCALC_INTERVAL } from "_APP_CONSTANTS"; -import { fromJS } from "immutable"; - -const createExampleLogs = callCount => { - let exampleAllLogs = []; - if (callCount <= 0) { - throw new Error("callCount argument must be at least 1"); - } else { - for (let i = 0; i < (callCount - 1) * AVERAGE_MEMCALC_INTERVAL; i++) { - const newLog = - Math.random() < 0.5 - ? exampleLongMessageInStore - : exampleShortMessageInStore; - exampleAllLogs.push(newLog); - } - exampleAllLogs = fromJS(exampleAllLogs); - return exampleAllLogs; - } -}; - -describe("LogMemoryCalculationReducer", () => { - // allLogs property gets passed through the thunk handleNewLog - test("> CALCULATE_LOG_MEMORY should calculate the averageSizeOfLog by doing a rough average calculation: only one log", () => { - // roughSizeOfObject(exampleShortMessageInStore) === 82 -> only log -> expect 82 (Byte) - expect( - logMemoryCalculation(initialState, exampleCalculateLogMemoryAction).get( - "averageSizeOfLog" - ) - ).toEqual(126); - }); - - test("> CALCULATE_LOG_MEMORY should cause rough calculations on every second log", () => { - /* The CALCULATE_LOG_MEMORY action is fired on every AVERAGE_MEMCALC_INTERVAL log. To simulate the second calculation - (first is on the first log), we need to pass a log list of AVERAGE_MEMCALC_INTERVAL length. */ - const exampleLogs = createExampleLogs(2); - const mockFn = jest.fn(); - // The implementation is not so expensive, mock functionality is just used for counting the calls. - mockFn.mockImplementation(roughSizeOfObject); - calculateAverageLogSize( - initialState.set("memCalcCounter", 1), // Simulate the second calculation - exampleLogs, - mockFn - ); - // 5 Logs in the list -> Calculate on every second -> 3 calculation calls are expected (first, third and fifth log) - expect(mockFn.mock.calls.length).toEqual(3); - }); - - test("> CALCULATE_LOG_MEMORY should continue from the last used index at the last calculation in all logs", () => { - // Third call starts the new average calculation at AVERAGE_MEMCALC_INTERVAL and ends at 2*AVERAGE_MEMCALC_INTERVAL - const exampleLogs = createExampleLogs(3); - const mockFn = jest.fn(); - mockFn.mockImplementation(roughSizeOfObject); - calculateAverageLogSize( - initialState.set("memCalcCounter", 2), // Simulate the third calculation - exampleLogs, - mockFn - ); - /* 10 Logs in the list -> Calculate on every second beginning at index 6 -> still 3 calculation calls are expected - (sixth, eighth and tenth log) */ - expect(mockFn.mock.calls.length).toEqual(3); - }); -}); diff --git a/developer-ui-frontend/src/reducers/__tests__/LogsReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/LogsReducer.test.js deleted file mode 100644 index 39ff5c2..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/LogsReducer.test.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import logsReducer, { initialState } from "../LogsReducer"; // initialState is already an Immutable Map -import { - exampleNewLogAction, - exampleRemoveOldestLogAction -} from "__mocks__/actionMocks"; -import { exampleState } from "__mocks__/storeMocks/stateMocks"; -import { calculateLogId } from "utils"; - -describe("LogsReducer", () => { - test("> NEW_LOG should parse the message to a format with id, time and message properties", () => { - const result = logsReducer(initialState, exampleNewLogAction); - const expectedLogId = calculateLogId(exampleNewLogAction.message); - const generatedLogObject = result.getIn(["byId", expectedLogId]).toJS(); - expect(generatedLogObject).toHaveProperty("id", expectedLogId); - expect(generatedLogObject).toHaveProperty("time"); - expect(generatedLogObject).toHaveProperty("message"); - }); - test("> NEW_LOG should parse the message itself to a format with type, deviceId, contentType, content and applicationProperties properties", () => { - const result = logsReducer(initialState, exampleNewLogAction); - const expectedLogId = calculateLogId(exampleNewLogAction.message); - const parsedMessage = result - .getIn(["byId", expectedLogId, "message"]) - .toJS(); - expect(parsedMessage).toHaveProperty("type"); - expect(parsedMessage).toHaveProperty("deviceId"); - expect(parsedMessage).toHaveProperty("contentType"); - expect(parsedMessage).toHaveProperty("content"); - expect(parsedMessage).toHaveProperty("applicationProperties"); - }); - test("> NEW_LOG should add the parsed and formatted message to byId and the log's id to allIds", () => { - const result = logsReducer(initialState, exampleNewLogAction); - const expectedLogId = calculateLogId(exampleNewLogAction.message); - expect(result.getIn(["allIds", 0])).toEqual(expectedLogId); - }); - const initialStateWithLogs = exampleState.get("logs"); - test("> REMOVE_OLDEST_LOG should delete an id in allIds", () => { - const initialAllLogsLength = initialStateWithLogs.get("allIds").size; - const result = logsReducer( - initialStateWithLogs, - exampleRemoveOldestLogAction - ); - expect(result.get("allIds").size).toEqual(initialAllLogsLength - 1); - }); - test("> REMOVE_OLDEST_LOG should delete the first id in allIds", () => { - const initiallySecondLogId = initialStateWithLogs.getIn(["allIds", 1]); - // Second log id should get first log id - const result = logsReducer( - initialStateWithLogs, - exampleRemoveOldestLogAction - ); - expect(result.getIn(["allIds", 0])).toEqual(initiallySecondLogId); - }); - test("> REMOVE_OLDEST_LOG should delete the corresponding log object in byId", () => { - const result = logsReducer( - initialStateWithLogs, - exampleRemoveOldestLogAction - ); - expect( - result.getIn(["byId", exampleRemoveOldestLogAction.id]) - ).toBeUndefined(); - }); - test("> REMOVE_OLDEST_LOG should use the log with the lowest timestamp i.e. this should always be first in allIds", () => { - const oldestId = initialStateWithLogs - .get("byId") - // Take the value of each key and push to an array - .toArray() - // find log with minimum timestamp - .reduce((curr, acc) => (curr.get("time") < acc.get("time") ? curr : acc)) - // get the logs id - .get("id"); - expect(initialStateWithLogs.getIn(["allIds", 0])).toEqual(oldestId); - }); -}); diff --git a/developer-ui-frontend/src/reducers/__tests__/SettingsReducer.test.js b/developer-ui-frontend/src/reducers/__tests__/SettingsReducer.test.js deleted file mode 100644 index 69e888a..0000000 --- a/developer-ui-frontend/src/reducers/__tests__/SettingsReducer.test.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2018 Bosch Software Innovations GmbH ("Bosch SI"). All rights reserved. - */ -import settingsReducer, { initialState } from '../SettingsReducer'; // initialState is already an Immutable Map -import { exampleChangeSortingAction } from '__mocks__/actionMocks'; - -describe('SettingsReducer', () => { - test('> CHANGE_SORTING should change to ascending if category is same as action.category', () => { - const initialStateSorted = initialState.setIn( - ['logsSorting', 'category'], - 'Device Id' - ); - // exampleChangeSortingAction has category 'Device Id' - const result = settingsReducer( - initialStateSorted, - exampleChangeSortingAction - ); - expect(result.getIn(['logsSorting', 'ascending'])).toBeTruthy(); - }); - test('> CHANGE_SORTING should keep descending order if category is not same as action.category', () => { - const initialStateSorted = initialState.setIn( - ['logsSorting', 'category'], - 'Type' - ); - // exampleChangeSortingAction has category 'Device Id' - const result = settingsReducer( - initialStateSorted, - exampleChangeSortingAction - ); - expect(result.getIn(['logsSorting', 'ascending'])).toBeFalsy(); - }); - test("> CHANGE_SORTING should change to 'unsorted' if category is same as action.category and order is already ascending", () => { - const initialStateSortedAscending = initialState.withMutations(state => - state - .setIn(['logsSorting', 'category'], 'Device Id') - .setIn(['logsSorting', 'ascending'], true) - ); - // exampleChangeSortingAction has category 'Device Id' - const result = settingsReducer( - initialStateSortedAscending, - exampleChangeSortingAction - ); - expect(result.getIn(['logsSorting', 'category'])).toEqual('unsorted'); - }); -}); diff --git a/developer-ui-frontend/src/reducers/selectors/DevicesSelectors.js b/developer-ui-frontend/src/reducers/selectors/DevicesSelectors.js index 573793b..4bc1ad4 100644 --- a/developer-ui-frontend/src/reducers/selectors/DevicesSelectors.js +++ b/developer-ui-frontend/src/reducers/selectors/DevicesSelectors.js @@ -61,6 +61,7 @@ export const selectPendingUnsubscribes = state => { }; export const selectNumberOfPendingUnsubscribes = state => selectPendingUnsubscribes(state).size; +export const selectAllDeviceIds = state => state.get("allIds"); export const selectAllDevices = state => state.get("allIds").map(id => state.getIn(["byId", id])); export const selectNumberOfAllDevices = state => state.get("allIds").size; diff --git a/developer-ui-frontend/src/reducers/selectors/index.js b/developer-ui-frontend/src/reducers/selectors/index.js index df9d72b..3829c35 100644 --- a/developer-ui-frontend/src/reducers/selectors/index.js +++ b/developer-ui-frontend/src/reducers/selectors/index.js @@ -8,7 +8,7 @@ import * as fromConnection from "./ConnectionSelectors"; import * as fromCredentials from "./CredentialsSelectors"; import { createSelector } from "reselect"; import { checkLog } from "utils"; -import { OrderedMap, fromJS } from "immutable"; +import { denormalizeCredential } from "api/schemas"; // Selector Shortcuts // Naming: @@ -49,6 +49,8 @@ export const selectCredentialIdsByDeviceId = (state, id) => fromDevices.selectCredentialIdsByDeviceId(state.get("devices"), id); export const selectRegistrationInfo = (state, id) => fromDevices.selectRegistrationInfo(state.get("devices"), id); +export const selectAllDeviceIds = state => + fromDevices.selectAllDeviceIds(state.get("devices")); export const selectAllFilters = state => fromFilter.selectAllFilters(state.get("filters")); @@ -108,40 +110,16 @@ export const selectVisibleLogs = createSelector( [selectAllFilters, selectAllLogs], (filters, logs) => logs.filter(log => checkLog(log, filters)) ); -// TODO: Reimplement as Reselect Memoized Selector -export const selectAllCredentialsApiFormat = (state, deviceId) => { - const credentialIds = selectCredentialIdsByDeviceId(state, deviceId); - const credentialsFromStore = credentialIds.map(credId => - selectCredentialById(state, credId) - ); - // (1) Add the device-id property, (2) get the correct secrets format and (3) bring it in the correct order - const allCreds = credentialsFromStore.map(credential => { - const secretsFromStore = selectSecretsByCredentialId( - state, - credential.get("auth-id") - ); - const unorderedMap = credential.withMutations(cred => - cred - .set("device-id", deviceId) // (1) - .set( - "secrets", - secretsFromStore.map(secret => { - return secret.delete("secretId"); - }) // (2) - ) - ); - // (3) - return OrderedMap({ - "device-id": unorderedMap.get("device-id"), - type: unorderedMap.get("type"), - "auth-id": unorderedMap.get("auth-id") - }).merge(unorderedMap); - }); - return allCreds; -}; -export const selectCredentialApiFormat = (state, deviceId, authId) => { - return selectAllCredentialsApiFormat(state, deviceId).find( - cred => cred.get("auth-id") === authId +export const selectDenormalizedCredential = (state, deviceId, authId) => + denormalizeCredential( + deviceId, + authId, + state.getIn(["credentials", "byId"]), + state.getIn(["credentials", "secrets", "byId"]) ); + +export const selectDenormalizedCredentials = (state, deviceId) => { + const authIds = selectCredentialIdsByDeviceId(state, deviceId); + return authIds.map(id => selectDenormalizedCredential(state, deviceId, id)); }; diff --git a/developer-ui-frontend/src/styles/app.scss b/developer-ui-frontend/src/styles/app.scss index 2c65054..cccb79c 100644 --- a/developer-ui-frontend/src/styles/app.scss +++ b/developer-ui-frontend/src/styles/app.scss @@ -4,6 +4,7 @@ @import "~pretty-checkbox/src/pretty-checkbox.scss"; html { + overflow: hidden; font-size: 62.5%; // For rem units support (62.5% of 16px = 10px -> easier calculations) font-smoothing: antialiased; -webkit-font-smoothing: antialiased; @@ -64,8 +65,6 @@ body { letter-spacing: 0.01rem; color: $primary_font_color; background-color: #f5f5f5; - overflow-x: hidden; - overflow-y: auto; background-image: url("https://s3.eu-central-1.amazonaws.com/developer-ui.bosch-iot-hub.com/assets/bosch_supergraphic_top.jpg"); background-position: bottom center; background-repeat: no-repeat; diff --git a/developer-ui-frontend/src/styles/credentialModal.scss b/developer-ui-frontend/src/styles/credentialModal.scss new file mode 100644 index 0000000..8f439fb --- /dev/null +++ b/developer-ui-frontend/src/styles/credentialModal.scss @@ -0,0 +1,302 @@ +@import "_globalVars.scss"; + +.edit-credential-modal { + height: 85vh !important; + max-height: 100vh; + .secrets-headerIcon { + opacity: 0.5; + } + .edit-credential-modal-body { + padding: 0px 6rem; + background: rgba(226, 226, 226, 0.45); + transform: translateZ(0); + } + + .arrow { + position: relative; + cursor: pointer; + fill: rgba(255, 255, 255, 0.8); + &.next { + transform: rotate(270deg); + } + &.previous { + transform: rotate(90deg); + margin-left: -0.8rem; + } + &.disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + .removeButtonDisabled { + fill: #757575; + } + + .secrets-messageRemove { + display: inline-flex; + + p { + margin: 0 0.5rem 0 0; + font-weight: 300; + } + button { + padding-top: 0; + padding-bottom: 0; + } + } + + .secrets-dropdown { + padding-left: 1%; + width: 100%; + margin: 1rem 0; + display: flex; + align-items: center; + label { + display: inline-block; + color: rgba(0, 0, 0, 0.54); + width: 11rem; + margin-right: 1rem; + font-weight: 600; + padding: 0.4rem 0; + } + } + + .secrets-previousButton { + position: relative; + top: 77px; + float: right; + } + + .secrets-nextButton { + position: relative; + top: 77px; + float: right; + } + + .enabled-switch { + font-size: 1.4rem; + } + + .standard-field { + width: 100%; + margin: 1rem 0; + display: flex; + align-items: center; + height: 2.8rem; + flex-wrap: wrap; + label { + display: inline-block; + color: rgba(0, 0, 0, 0.54); + width: 11rem; + margin-right: 1rem; + font-weight: 600; + padding: 0.4rem 0; + } + .field-dropdown { + display: inline-block; + } + } + + .general-info { + width: 100%; + flex: 1; + margin-top: 5%; + + .standard-field { + margin: 1rem; + } + } + .secrets-config { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 2px 0 rgba(0, 0, 0, 0.24); + background-color: #fff; + position: relative; + width: 100%; + flex: 2; + margin: 5% 0; + min-height: 33.6rem; + + form[name="addNewSecret"] { + .secret { + background-color: #fff !important; + } + } + + form { + position: absolute; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + background-color: #c2c2c2; + + .secrets-nav-header { + display: flex; + justify-content: space-between; + padding: 1rem; + background: #464f53; + border: 1px solid #464f53; + color: #fff; + margin: 0 -1px; + z-index: 3; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), + 0 6px 6px rgba(0, 0, 0, 0.23); + + .secrets-label { + display: inline-flex; + align-items: center; + position: relative; + z-index: 2; + height: 2.5rem; + white-space: nowrap; + user-select: none; + svg.add-pw-icon { + height: 2rem; + width: 2rem; + margin-right: 1rem; + path { + fill: rgba(255, 255, 255, 0.8); + } + } + } + .cancel-button { + background: none; + border: none; + font-size: 2rem; + cursor: pointer; + outline: none; + color: rgba(255, 255, 255, 0.8); + transform: rotate(45deg); + } + .secrets-buttons { + display: inline-flex; + position: relative; + z-index: 2; + svg { + height: 2.25rem; + width: 2.25rem; + cursor: pointer; + path { + fill: rgba(255, 255, 255, 0.8); + } + &.secrets-editButton { + padding-right: 1.5em; + margin-right: 0.5em; + border-right: 1px solid rgba(255, 255, 255, 0.3); + } + &.disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + } + } + .secrets-config-inner { + display: flex; + position: relative; + width: 100%; + flex: 1; + .secret { + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #c2c2c2; + position: absolute; + transition: transform 0.35s cubic-bezier(0, 0, 0.2, 1); + width: 100%; + height: 100%; + .animated-card { + height: 100%; + background-color: #fff; + opacity: 0; + box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.24); + transition: box-shadow 0.35s cubic-bezier(0, 0, 0.2, 1), + opacity 0.35s cubic-bezier(0, 0, 0.2, 1); + &.selected { + z-index: 1; + animation: upAndIn 0.35s cubic-bezier(0, 0, 0.2, 1) forwards; + transform-origin: 50% 50%; + opacity: 1; + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.24); + } + } + .secrets-standard-fields { + position: relative; + margin-top: 1rem; + padding: 0 1rem; + } + .secrets-advanced-options { + display: flex; + flex-direction: column; + margin-bottom: 3.5rem; + padding: 0 1rem 1rem 1rem; + .expand-link { + margin: 1.4rem 0; + } + .advanced-headline { + color: $accent_blue; + } + .salt-input-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + input { + flex: 1; + } + a { + cursor: pointer; + transition: opacity 0.2s ease-out; + color: $accent_blue; + opacity: 0.5; + font-weight: 300; + margin-right: 1rem; + white-space: nowrap; + margin: 0 0 0 1rem; + &:hover { + opacity: 1; + } + } + } + .validity-period-wrapper { + display: flex; + align-items: center; + label { + display: inline-block; + color: rgba(0, 0, 0, 0.54); + width: 11rem; + margin-right: 1rem; + font-weight: 600; + padding: 0.4rem 0; + } + .standard-field { + flex: 1; + } + .date-picker-wrapper { + flex: 1; + &:first-of-type { + margin-right: 3rem; + } + } + } + } + .fixed-footer { + position: absolute; + bottom: 1rem; + width: calc(100% - 2rem); + height: 2.5rem; + align-items: center; + display: flex; + justify-content: flex-end; + button { + font-size: 1.4rem; + padding: 0 0.5rem 0 0; + svg { + height: 0.525em; + } + } + } + } + } + } + } +} diff --git a/developer-ui-frontend/src/styles/datePicker.scss b/developer-ui-frontend/src/styles/datePicker.scss index 9ed58e2..24de1e2 100644 --- a/developer-ui-frontend/src/styles/datePicker.scss +++ b/developer-ui-frontend/src/styles/datePicker.scss @@ -6,6 +6,10 @@ .date-picker-wrapper { position: relative; + .iconSpace { + padding-right: 25px; + } + svg { cursor: pointer; position: absolute; @@ -168,7 +172,7 @@ } .react-datepicker-popper { - z-index: 1; + z-index: 99999999999; } .react-datepicker-popper[data-placement^="bottom"] { diff --git a/developer-ui-frontend/src/styles/jsonEditor.scss b/developer-ui-frontend/src/styles/jsonEditor.scss index a1f2b0a..05fe028 100644 --- a/developer-ui-frontend/src/styles/jsonEditor.scss +++ b/developer-ui-frontend/src/styles/jsonEditor.scss @@ -42,6 +42,9 @@ div.jsoneditor .jsoneditor-search input { .jsoneditor-menu { animation: fade-out 0.2s ease-out forwards; } + .jsoneditor-text-errors { + display: none; + } .jsoneditor-outer { margin-left: 0; // gets overwritten by the slide-out animation @@ -72,7 +75,6 @@ div.jsoneditor table { border-collapse: collapse; width: auto; } - div.jsoneditor td, div.jsoneditor th { padding: 0; @@ -279,7 +281,7 @@ div.jsoneditor-container { height: 100%; margin: -40px 0 0 0; margin-left: -41px; // gets overwritten by forwards slide-in animation - padding: 40px 0 0 0; + padding: 40px 0 0 0 !important; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; @@ -378,9 +380,13 @@ div.jsoneditor td, div.jsoneditor th, div.jsoneditor textarea, .jsoneditor-schema-error { - font-family: "Roboto Mono", monospace; - font-size: 10pt; - color: #1a1a1a; + font-family: "Roboto", "RobotoDraft", "Helvetica Neue", "Helvetica", "Arial", + Sans-Serif; + font-size: 1.3rem; + color: #000; + letter-spacing: 0.01071em; + font-weight: 400; + line-height: 1.5; } /* popover */ @@ -546,10 +552,45 @@ div.jsoneditor-tree .jsoneditor-schema-error { /* JSON schema errors displayed at the bottom of the editor in mode text and code */ .jsoneditor .jsoneditor-text-errors { - width: 100%; + position: absolute; + top: 40px; + left: 50%; + z-index: 4; border-collapse: collapse; - background-color: #ffef8b; - border-top: 1px solid #ffd700; + background-color: rgba(255, 239, 139, 0.65); + animation: slideDown 0.2s ease-out forwards; + margin-top: 0.5rem; + border-radius: 0.4rem; + @keyframes slideDown { + 0% { + transform: translate(-50%, -100%); + } + 100% { + transform: translate(-50%, 0); + } + } +} +.jsoneditor .jsoneditor-text-errors tr { + display: flex; + align-items: center; + height: 3.5rem; + padding-right: 1rem; + + &:not(:first-of-type) { + display: none; + } + + td { + &:empty { + display: none; + } + pre { + margin: 0; + } + button { + vertical-align: middle; + } + } } .jsoneditor .jsoneditor-text-errors td { diff --git a/developer-ui-frontend/src/styles/registrations.scss b/developer-ui-frontend/src/styles/registrations.scss index 44e7aa2..35fef12 100644 --- a/developer-ui-frontend/src/styles/registrations.scss +++ b/developer-ui-frontend/src/styles/registrations.scss @@ -219,7 +219,7 @@ cursor: pointer; outline: none; text-decoration: none; - color: $disabled_font_color; + color: rgba(0, 0, 0, 0.55); z-index: 1; list-style: none; padding: 1rem; @@ -229,6 +229,11 @@ transition: color 80ms ease-in; user-select: none; + &.disabled { + cursor: not-allowed; + color: $disabled_font_color; + } + &:before { content: ""; position: absolute; diff --git a/developer-ui-frontend/src/utils/utils.js b/developer-ui-frontend/src/utils/utils.js index d07edae..c5cee51 100644 --- a/developer-ui-frontend/src/utils/utils.js +++ b/developer-ui-frontend/src/utils/utils.js @@ -3,7 +3,6 @@ */ import uuid from "uuid/v4"; import _ from "lodash"; -import jssha from "jssha"; export const camelCase = str => { return str @@ -27,14 +26,6 @@ export const formatDateString = date => { export const calculateLogId = message => JSON.parse(message.body).deviceId + "-" + message.timestamp; -export const calculateSecretId = (secretObj, authId) => { - // secretHashes must be deterministic for testing -> use a hash function - const hashObj = new jssha("SHA-1", "TEXT"); - hashObj.update(JSON.stringify(secretObj)); - const hashValue = hashObj.getHash("B64"); - return authId + "-" + hashValue; -}; - export const calculateFilterId = (category, value) => category.toLowerCase() + "-" + value.toLowerCase(); @@ -84,46 +75,6 @@ export const checkLog = (log, filterList) => { return !filteredOut; }; -// Maps the entered form params to the schema defined by the CredentialsReducer -export const mapCredentialParams = (authId, credType, data, secrets) => { - const newAuthId = authId; - const newType = credType; - let newCredential = { - "auth-id": newAuthId, - type: newType, - secrets: secrets ? secrets : [] - }; - if (data) { - newCredential = { ...newCredential, ...data }; - } - return newCredential; -}; -// Maps the entered form params to the schema defined by the CredentialsReducer -export const mapSecretParams = (secretId, authId, secretType, data) => { - const newSecretType = secretType; - let newSecret = null; - if (newSecretType === "Hashed Password") { - const { hashMethod, password, ...other } = data; - let pw = ""; - try { - const method = hashMethod; - const hashObj = new jssha(method.toUpperCase(), "TEXT"); - hashObj.update(password); - pw = hashObj.getHash("B64"); - newSecret = { - "hash-function": method, - "pwd-hash": pw, - ...other - }; - } catch (err) { - console.error(err); - } - } - const newSecretId = secretId || calculateSecretId(newSecret, authId); - newSecret = { ...newSecret, secretId: newSecretId }; - return newSecret; -}; - export const deepDiff = (object, base) => { const changes = (obj, bs) => { return _.transform(obj, (result, value, key) => { @@ -137,9 +88,3 @@ export const deepDiff = (object, base) => { }; return changes(object, base); }; - -export const arrayToObject = (array, keyField) => - array.reduce((obj, item) => { - obj[item[keyField]] = item; - return obj; - }, {}); diff --git a/developer-ui-frontend/webpack.config.js b/developer-ui-frontend/webpack.config.js index 1cfc482..675e230 100644 --- a/developer-ui-frontend/webpack.config.js +++ b/developer-ui-frontend/webpack.config.js @@ -74,7 +74,7 @@ module.exports = { filename: "app.bundle.js", publicPath: publicPathConfig }, - stats: { errorDetails: true }, + stats: "verbose", devtool: isProd ? "source-map" : "cheap-module-source-map", module: { rules: [ diff --git a/developer-ui-frontend/yarn.lock b/developer-ui-frontend/yarn.lock index c34c111..b383b7c 100644 --- a/developer-ui-frontend/yarn.lock +++ b/developer-ui-frontend/yarn.lock @@ -403,6 +403,15 @@ ajv@5.5.2, ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ajv@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.4.tgz#247d5274110db653706b550fcc2b797ca28cfc59" + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" @@ -473,6 +482,13 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -508,10 +524,18 @@ arr-diff@^2.0.0: dependencies: arr-flatten "^1.0.1" -arr-flatten@^1.0.1: +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + array-differ@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" @@ -553,6 +577,10 @@ array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + array.prototype.flatmap@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.1.1.tgz#dbb6c44693c2a2a2fcab24e551dfbf47f67fde03" @@ -603,6 +631,10 @@ assert@^1.1.1: dependencies: util "0.10.3" +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + ast-types@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd" @@ -637,10 +669,20 @@ async@^2.1.2, async@^2.1.4, async@^2.1.5: dependencies: lodash "^4.14.0" +async@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + dependencies: + lodash "^4.17.10" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + atob@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" @@ -896,13 +938,20 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^22.0.3, babel-jest@^22.1.0: +babel-jest@^22.0.3: version "22.1.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.1.0.tgz#7fae6f655fffe77e818a8c2868c754a42463fdfd" dependencies: babel-plugin-istanbul "^4.1.5" babel-preset-jest "^22.1.0" +babel-jest@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1" + dependencies: + babel-plugin-istanbul "^4.1.6" + babel-preset-jest "^23.2.0" + babel-loader@^7.1.1, babel-loader@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.2.tgz#f6cbe122710f1aa2af4d881c6d5b54358ca24126" @@ -945,10 +994,23 @@ babel-plugin-istanbul@^4.1.5: istanbul-lib-instrument "^1.7.5" test-exclude "^4.1.1" +babel-plugin-istanbul@^4.1.6: + version "4.1.6" + resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" + find-up "^2.1.0" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" + babel-plugin-jest-hoist@^22.1.0: version "22.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.1.0.tgz#c1281dd7887d77a1711dc760468c3b8285dde9ee" +babel-plugin-jest-hoist@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167" + babel-plugin-minify-builtins@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.2.0.tgz#317f824b0907210b6348671bb040ca072e2e0c82" @@ -1493,6 +1555,13 @@ babel-preset-jest@^22.1.0: babel-plugin-jest-hoist "^22.1.0" babel-plugin-syntax-object-rest-spread "^6.13.0" +babel-preset-jest@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46" + dependencies: + babel-plugin-jest-hoist "^23.2.0" + babel-plugin-syntax-object-rest-spread "^6.13.0" + babel-preset-minify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.2.0.tgz#006566552d9b83834472273f306c0131062a0acc" @@ -1618,7 +1687,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -1632,7 +1701,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: +babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -1669,6 +1738,18 @@ base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -1770,6 +1851,21 @@ body-parser@1.18.2: raw-body "2.3.2" type-is "~1.6.15" +body-parser@1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -1819,6 +1915,21 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +braces@^2.3.0, braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + brcast@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/brcast/-/brcast-3.0.1.tgz#6256a8349b20de9eed44257a9b24d71493cd48dd" @@ -1831,9 +1942,9 @@ browser-process-hrtime@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e" -browser-resolve@^1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" dependencies: resolve "1.1.7" @@ -1913,6 +2024,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + buffer-to-vinyl@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz#00f15faee3ab7a1dda2cde6d9121bffdd07b2262" @@ -1971,6 +2086,20 @@ cacache@^10.0.1: unique-filename "^1.1.0" y18n "^3.2.1" +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" @@ -2112,10 +2241,33 @@ chokidar@^1.6.0, chokidar@^1.7.0: optionalDependencies: fsevents "^1.0.0" +chokidar@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + lodash.debounce "^4.0.8" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.5" + optionalDependencies: + fsevents "^1.2.2" + chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + ci-info@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" @@ -2137,6 +2289,15 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + classnames@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" @@ -2228,6 +2389,13 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + color-convert@^1.3.0, color-convert@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" @@ -2306,6 +2474,10 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + compressible@~2.0.11: version "2.0.12" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" @@ -2404,6 +2576,10 @@ copy-concurrently@^1.0.0: rimraf "^2.5.4" run-queue "^1.0.0" +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + copy-to-clipboard@^3: version "3.0.8" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" @@ -2533,6 +2709,10 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^3.1.9-1: + version "3.1.9-1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -2704,7 +2884,7 @@ dateformat@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" -debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.6.8: +debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -2788,6 +2968,10 @@ deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -2809,6 +2993,25 @@ define-properties@^1.1.2: foreach "^2.0.5" object-keys "^1.0.8" +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" @@ -3476,30 +3679,42 @@ expand-brackets@^0.1.4: dependencies: is-posix-bracket "^0.1.0" +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + expand-range@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" dependencies: fill-range "^2.1.0" -expect@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-22.1.0.tgz#f8f9b019ab275d859cbefed531fbaefe8972431d" +expect@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-23.6.0.tgz#1e0c8d3ba9a581c87bd71fb9bc8862d443425f98" dependencies: ansi-styles "^3.2.0" - jest-diff "^22.1.0" + jest-diff "^23.6.0" jest-get-type "^22.1.0" - jest-matcher-utils "^22.1.0" - jest-message-util "^22.1.0" - jest-regex-util "^22.1.0" + jest-matcher-utils "^23.6.0" + jest-message-util "^23.4.0" + jest-regex-util "^23.3.0" express@^4.13.3: - version "4.16.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + version "4.16.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" dependencies: accepts "~1.3.5" array-flatten "1.1.1" - body-parser "1.18.2" + body-parser "1.18.3" content-disposition "0.5.2" content-type "~1.0.4" cookie "0.3.1" @@ -3516,10 +3731,10 @@ express@^4.13.3: on-finished "~2.3.0" parseurl "~1.3.2" path-to-regexp "0.1.7" - proxy-addr "~2.0.3" - qs "6.5.1" + proxy-addr "~2.0.4" + qs "6.5.2" range-parser "~1.2.0" - safe-buffer "5.1.1" + safe-buffer "5.1.2" send "0.16.2" serve-static "1.13.2" setprototypeof "1.1.0" @@ -3569,6 +3784,13 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -3579,6 +3801,19 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + extract-text-webpack-plugin@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.2.tgz#756ef4efa8155c3681833fbc34da53b941746d6c" @@ -3608,6 +3843,10 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -3740,6 +3979,15 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -3754,7 +4002,7 @@ finalhandler@1.1.0: finalhandler@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + resolved "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" dependencies: debug "2.6.9" encodeurl "~1.0.2" @@ -3836,7 +4084,7 @@ for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" -for-in@^1.0.1: +for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -3880,6 +4128,12 @@ forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3891,6 +4145,12 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -3911,6 +4171,13 @@ fsevents@^1.0.0, fsevents@^1.1.1: nan "^2.3.0" node-pre-gyp "^0.6.39" +fsevents@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + fstream-ignore@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" @@ -3995,6 +4262,10 @@ get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -4044,7 +4315,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob-parent@^3.0.0: +glob-parent@^3.0.0, glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" dependencies: @@ -4314,6 +4585,33 @@ has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + has@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" @@ -4480,7 +4778,7 @@ html-tag-names@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.2.tgz#f65168964c5a9c82675efda882875dcb2a875c22" -html-webpack-plugin@^2.28.0, html-webpack-plugin@^2.30.1: +html-webpack-plugin@^2.30.1: version "2.30.1" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5" dependencies: @@ -4491,6 +4789,18 @@ html-webpack-plugin@^2.28.0, html-webpack-plugin@^2.30.1: pretty-error "^2.0.2" toposort "^1.0.0" +html-webpack-plugin@^3.2.0: + version "3.2.0" + resolved "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" + dependencies: + html-minifier "^3.2.3" + loader-utils "^0.2.16" + lodash "^4.17.3" + pretty-error "^2.0.2" + tapable "^1.0.0" + toposort "^1.0.0" + util.promisify "1.0.0" + htmlparser2@3.9.1: version "3.9.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.1.tgz#621b7a58bc9acd003f7af0a2c9a00aa67c8505d2" @@ -4535,6 +4845,15 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-errors@1.6.3, http-errors@~1.6.3: + version "1.6.3" + resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.4.0: version "0.4.9" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" @@ -4583,6 +4902,18 @@ iconv-lite@0.4.19, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + dependencies: + safer-buffer ">= 2.1.2 < 3" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -4601,6 +4932,12 @@ iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" + ignore@^3.1.2: version "3.3.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" @@ -4771,7 +5108,7 @@ invariant@2.2.2, invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2: dependencies: loose-envify "^1.0.0" -invariant@^2.2.0: +invariant@^2.2.0, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -4789,9 +5126,9 @@ ipaddr.js@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" -ipaddr.js@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" +ipaddr.js@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" is-absolute-url@^2.0.0: version "2.1.0" @@ -4803,6 +5140,18 @@ is-absolute@^0.1.5: dependencies: is-relative "^0.1.0" +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4847,10 +5196,38 @@ is-cwebp-readable@^2.0.1: dependencies: file-type "^4.3.0" +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" @@ -4873,11 +5250,17 @@ is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4921,6 +5304,12 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-gzip@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" @@ -4982,7 +5371,7 @@ is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" -is-plain-object@^2.0.1, is-plain-object@^2.0.4: +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" dependencies: @@ -5076,6 +5465,10 @@ is-windows@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + is-zip@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-zip/-/is-zip-1.0.0.tgz#47b0a8ff4d38a76431ccfd99a8e15a4c86ba2325" @@ -5098,7 +5491,7 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" -isobject@^3.0.1: +isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" @@ -5113,18 +5506,18 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -istanbul-api@^1.1.14: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" +istanbul-api@^1.3.1: + version "1.3.7" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa" dependencies: async "^2.1.4" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.1.0" - istanbul-lib-instrument "^1.9.1" - istanbul-lib-report "^1.1.2" - istanbul-lib-source-maps "^1.2.2" - istanbul-reports "^1.1.3" + istanbul-lib-coverage "^1.2.1" + istanbul-lib-hook "^1.2.2" + istanbul-lib-instrument "^1.10.2" + istanbul-lib-report "^1.1.5" + istanbul-lib-source-maps "^1.2.6" + istanbul-reports "^1.5.1" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" @@ -5133,13 +5526,29 @@ istanbul-lib-coverage@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" -istanbul-lib-hook@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" +istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" + +istanbul-lib-hook@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0, istanbul-lib-instrument@^1.9.1: +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.2.1" + semver "^5.3.0" + +istanbul-lib-instrument@^1.7.5: version "1.9.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" dependencies: @@ -5151,28 +5560,28 @@ istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0, istanbul-lib-ins istanbul-lib-coverage "^1.1.1" semver "^5.3.0" -istanbul-lib-report@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" +istanbul-lib-report@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c" dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.2.1, istanbul-lib-source-maps@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" +istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" +istanbul-reports@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" dependencies: handlebars "^4.0.3" @@ -5180,15 +5589,15 @@ javascript-natural-sort@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" -jest-changed-files@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.1.0.tgz#586a6164b87255dbd541a8bab880d98f14c99b7d" +jest-changed-files@^23.4.2: + version "23.4.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83" dependencies: throat "^4.0.0" -jest-cli@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.1.2.tgz#89497932d7befb8a6952f2712473695c4bbef43f" +jest-cli@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" @@ -5197,48 +5606,54 @@ jest-cli@^22.1.2: graceful-fs "^4.1.11" import-local "^1.0.0" is-ci "^1.0.10" - istanbul-api "^1.1.14" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-instrument "^1.8.0" - istanbul-lib-source-maps "^1.2.1" - jest-changed-files "^22.1.0" - jest-config "^22.1.2" - jest-environment-jsdom "^22.1.2" + istanbul-api "^1.3.1" + istanbul-lib-coverage "^1.2.0" + istanbul-lib-instrument "^1.10.1" + istanbul-lib-source-maps "^1.2.4" + jest-changed-files "^23.4.2" + jest-config "^23.6.0" + jest-environment-jsdom "^23.4.0" jest-get-type "^22.1.0" - jest-haste-map "^22.1.0" - jest-message-util "^22.1.0" - jest-regex-util "^22.1.0" - jest-resolve-dependencies "^22.1.0" - jest-runner "^22.1.2" - jest-runtime "^22.1.2" - jest-snapshot "^22.1.2" - jest-util "^22.1.2" - jest-worker "^22.1.0" + jest-haste-map "^23.6.0" + jest-message-util "^23.4.0" + jest-regex-util "^23.3.0" + jest-resolve-dependencies "^23.6.0" + jest-runner "^23.6.0" + jest-runtime "^23.6.0" + jest-snapshot "^23.6.0" + jest-util "^23.4.0" + jest-validate "^23.6.0" + jest-watcher "^23.4.0" + jest-worker "^23.2.0" micromatch "^2.3.11" - node-notifier "^5.1.2" + node-notifier "^5.2.1" + prompts "^0.1.9" realpath-native "^1.0.0" rimraf "^2.5.4" slash "^1.0.0" string-length "^2.0.0" strip-ansi "^4.0.0" which "^1.2.12" - yargs "^10.0.3" + yargs "^11.0.0" -jest-config@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.1.2.tgz#d3aee5c1df0997f0e2ae5c707eee04a7c87f1653" +jest-config@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d" dependencies: + babel-core "^6.0.0" + babel-jest "^23.6.0" chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^22.1.2" - jest-environment-node "^22.1.2" + jest-environment-jsdom "^23.4.0" + jest-environment-node "^23.4.0" jest-get-type "^22.1.0" - jest-jasmine2 "^22.1.2" - jest-regex-util "^22.1.0" - jest-resolve "^22.1.0" - jest-util "^22.1.2" - jest-validate "^22.1.2" - pretty-format "^22.1.0" + jest-jasmine2 "^23.6.0" + jest-regex-util "^23.3.0" + jest-resolve "^23.6.0" + jest-util "^23.4.0" + jest-validate "^23.6.0" + micromatch "^2.3.11" + pretty-format "^23.6.0" jest-diff@^22.1.0: version "22.1.0" @@ -5249,63 +5664,82 @@ jest-diff@^22.1.0: jest-get-type "^22.1.0" pretty-format "^22.1.0" -jest-docblock@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.1.0.tgz#3fe5986d5444cbcb149746eb4b07c57c5a464dfd" +jest-diff@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d" + dependencies: + chalk "^2.0.1" + diff "^3.2.0" + jest-get-type "^22.1.0" + pretty-format "^23.6.0" + +jest-docblock@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7" dependencies: detect-newline "^2.1.0" -jest-environment-jsdom@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.1.2.tgz#4488c629631dd5de9059ec747fcd358735247f70" +jest-each@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575" + dependencies: + chalk "^2.0.1" + pretty-format "^23.6.0" + +jest-environment-jsdom@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023" dependencies: - jest-mock "^22.1.0" - jest-util "^22.1.2" + jest-mock "^23.2.0" + jest-util "^23.4.0" jsdom "^11.5.1" -jest-environment-node@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.1.2.tgz#77dc32fbe08caa03ef2acb0948dce4b25a14633a" +jest-environment-node@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10" dependencies: - jest-mock "^22.1.0" - jest-util "^22.1.2" + jest-mock "^23.2.0" + jest-util "^23.4.0" jest-get-type@^22.1.0: version "22.1.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.1.0.tgz#4e90af298ed6181edc85d2da500dbd2753e0d5a9" -jest-haste-map@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.1.0.tgz#1174c6ff393f9818ebf1163710d8868b5370da2a" +jest-haste-map@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.6.0.tgz#2e3eb997814ca696d62afdb3f2529f5bbc935e16" dependencies: fb-watchman "^2.0.0" graceful-fs "^4.1.11" - jest-docblock "^22.1.0" - jest-worker "^22.1.0" + invariant "^2.2.4" + jest-docblock "^23.2.0" + jest-serializer "^23.0.1" + jest-worker "^23.2.0" micromatch "^2.3.11" sane "^2.0.0" -jest-jasmine2@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.1.2.tgz#dee4ba04fb2cf462e4c7cfb499426e82e8fde5ac" +jest-jasmine2@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz#840e937f848a6c8638df24360ab869cc718592e0" dependencies: - callsites "^2.0.0" + babel-traverse "^6.0.0" chalk "^2.0.1" co "^4.6.0" - expect "^22.1.0" - graceful-fs "^4.1.11" + expect "^23.6.0" is-generator-fn "^1.0.0" - jest-diff "^22.1.0" - jest-matcher-utils "^22.1.0" - jest-message-util "^22.1.0" - jest-snapshot "^22.1.2" - source-map-support "^0.5.0" + jest-diff "^23.6.0" + jest-each "^23.6.0" + jest-matcher-utils "^23.6.0" + jest-message-util "^23.4.0" + jest-snapshot "^23.6.0" + jest-util "^23.4.0" + pretty-format "^23.6.0" -jest-leak-detector@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.1.0.tgz#08376644cee07103da069baac19adb0299b772c2" +jest-leak-detector@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de" dependencies: - pretty-format "^22.1.0" + pretty-format "^23.6.0" jest-matcher-utils@^22.1.0: version "22.1.0" @@ -5315,9 +5749,17 @@ jest-matcher-utils@^22.1.0: jest-get-type "^22.1.0" pretty-format "^22.1.0" -jest-message-util@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.1.0.tgz#51ba0794cb6e579bfc4e9adfac452f9f1a0293fc" +jest-matcher-utils@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz#726bcea0c5294261a7417afb6da3186b4b8cac80" + dependencies: + chalk "^2.0.1" + jest-get-type "^22.1.0" + pretty-format "^23.6.0" + +jest-message-util@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f" dependencies: "@babel/code-frame" "^7.0.0-beta.35" chalk "^2.0.1" @@ -5325,68 +5767,78 @@ jest-message-util@^22.1.0: slash "^1.0.0" stack-utils "^1.0.1" -jest-mock@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.1.0.tgz#87ec21c0599325671c9a23ad0e05c86fb5879b61" +jest-mock@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134" -jest-regex-util@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.1.0.tgz#5daf2fe270074b6da63e5d85f1c9acc866768f53" +jest-regex-util@^23.3.0: + version "23.3.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5" -jest-resolve-dependencies@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.1.0.tgz#340e4139fb13315cd43abc054e6c06136be51e31" +jest-resolve-dependencies@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz#b4526af24c8540d9a3fab102c15081cf509b723d" dependencies: - jest-regex-util "^22.1.0" + jest-regex-util "^23.3.0" + jest-snapshot "^23.6.0" -jest-resolve@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.1.0.tgz#5f4307f48b93c1abdbeacc9ed80642ffcb246294" +jest-resolve@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae" dependencies: - browser-resolve "^1.11.2" + browser-resolve "^1.11.3" chalk "^2.0.1" + realpath-native "^1.0.0" -jest-runner@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.1.2.tgz#e8565e4eb56c27219352b8486338ced9ceb462da" +jest-runner@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38" dependencies: exit "^0.1.2" - jest-config "^22.1.2" - jest-docblock "^22.1.0" - jest-haste-map "^22.1.0" - jest-jasmine2 "^22.1.2" - jest-leak-detector "^22.1.0" - jest-message-util "^22.1.0" - jest-runtime "^22.1.2" - jest-util "^22.1.2" - jest-worker "^22.1.0" + graceful-fs "^4.1.11" + jest-config "^23.6.0" + jest-docblock "^23.2.0" + jest-haste-map "^23.6.0" + jest-jasmine2 "^23.6.0" + jest-leak-detector "^23.6.0" + jest-message-util "^23.4.0" + jest-runtime "^23.6.0" + jest-util "^23.4.0" + jest-worker "^23.2.0" + source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.1.2.tgz#42755d7cea2ffc7cdaa7f2dfa8736264a6057bf9" +jest-runtime@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082" dependencies: babel-core "^6.0.0" - babel-jest "^22.1.0" - babel-plugin-istanbul "^4.1.5" + babel-plugin-istanbul "^4.1.6" chalk "^2.0.1" convert-source-map "^1.4.0" exit "^0.1.2" + fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.11" - jest-config "^22.1.2" - jest-haste-map "^22.1.0" - jest-regex-util "^22.1.0" - jest-resolve "^22.1.0" - jest-util "^22.1.2" - json-stable-stringify "^1.0.1" + jest-config "^23.6.0" + jest-haste-map "^23.6.0" + jest-message-util "^23.4.0" + jest-regex-util "^23.3.0" + jest-resolve "^23.6.0" + jest-snapshot "^23.6.0" + jest-util "^23.4.0" + jest-validate "^23.6.0" micromatch "^2.3.11" realpath-native "^1.0.0" slash "^1.0.0" strip-bom "3.0.0" write-file-atomic "^2.1.0" - yargs "^10.0.3" + yargs "^11.0.0" -jest-snapshot@>=20.0.3, jest-snapshot@^22.1.2: +jest-serializer@^23.0.1: + version "23.0.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165" + +jest-snapshot@>=20.0.3: version "22.1.2" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.1.2.tgz#b270cf6e3098f33aceeafda02b13eb0933dc6139" dependencies: @@ -5397,44 +5849,69 @@ jest-snapshot@>=20.0.3, jest-snapshot@^22.1.2: natural-compare "^1.4.0" pretty-format "^22.1.0" +jest-snapshot@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.6.0.tgz#f9c2625d1b18acda01ec2d2b826c0ce58a5aa17a" + dependencies: + babel-types "^6.0.0" + chalk "^2.0.1" + jest-diff "^23.6.0" + jest-matcher-utils "^23.6.0" + jest-message-util "^23.4.0" + jest-resolve "^23.6.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^23.6.0" + semver "^5.5.0" + jest-specific-snapshot@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-0.3.0.tgz#89d37c7c2e94180c7b58bfedf9d9dc172ebd19e3" dependencies: jest-snapshot ">=20.0.3" -jest-util@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.1.2.tgz#4bf098f651e8611d744cefa23fa026c97a6a3d5d" +jest-util@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561" dependencies: callsites "^2.0.0" chalk "^2.0.1" graceful-fs "^4.1.11" is-ci "^1.0.10" - jest-message-util "^22.1.0" - jest-validate "^22.1.2" + jest-message-util "^23.4.0" mkdirp "^0.5.1" + slash "^1.0.0" + source-map "^0.6.0" -jest-validate@^22.1.2: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.1.2.tgz#c3b06bcba7bd9a850919fe336b5f2a8c3a239404" +jest-validate@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474" dependencies: chalk "^2.0.1" jest-get-type "^22.1.0" leven "^2.1.0" - pretty-format "^22.1.0" + pretty-format "^23.6.0" -jest-worker@^22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.1.0.tgz#0987832fe58fbdc205357f4c19b992446368cafb" +jest-watcher@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.1" + string-length "^2.0.0" + +jest-worker@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9" dependencies: merge-stream "^1.0.1" -jest@^22.0.3: - version "22.1.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-22.1.2.tgz#54dce0f4946a089a00d5fdac8291d5926e24f6ab" +jest@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d" dependencies: - jest-cli "^22.1.2" + import-local "^1.0.0" + jest-cli "^23.6.0" jmespath@0.15.0: version "0.15.0" @@ -5519,6 +5996,10 @@ json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -5582,10 +6063,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jssha@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jssha/-/jssha-2.3.1.tgz#147b2125369035ca4b2f7d210dc539f009b3de9a" - jsx-ast-utils@^1.2.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" @@ -5600,7 +6077,7 @@ kind-of@^2.0.1: dependencies: is-buffer "^1.0.2" -kind-of@^3.0.2, kind-of@^3.2.2: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" dependencies: @@ -5612,6 +6089,18 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +kleur@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" + lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -5881,9 +6370,9 @@ lodash@^4.14.0, lodash@^4.17.5: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" -lodash@^4.17.2: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" +lodash@^4.17.10, lodash@^4.17.2: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" logalot@^2.0.0: version "2.1.0" @@ -5953,10 +6442,20 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + markdown-loader@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/markdown-loader/-/markdown-loader-2.0.2.tgz#1cdcf11307658cd611046d7db34c2fe80542af7c" @@ -6055,6 +6554,24 @@ micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" +micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -6070,9 +6587,9 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" +mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" @@ -6081,10 +6598,10 @@ mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-db "~1.30.0" mime-types@~2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" dependencies: - mime-db "~1.33.0" + mime-db "~1.37.0" mime@1.4.1: version "1.4.1" @@ -6134,6 +6651,19 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" + dependencies: + minipass "^2.2.1" + mississippi@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-1.3.0.tgz#d201583eb12327e3c5c1642a404a9cacf94e34f5" @@ -6149,6 +6679,13 @@ mississippi@^1.3.0: stream-each "^1.1.0" through2 "^2.0.0" +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + mixin-object@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" @@ -6207,6 +6744,26 @@ nan@^2.3.0, nan@^2.3.2: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" +nan@^2.9.2: + version "2.11.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -6225,10 +6782,22 @@ nearley@^2.7.10: railroad-diagrams "^1.0.0" randexp "^0.4.2" +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +neo-async@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + nested-object-assign@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nested-object-assign/-/nested-object-assign-1.0.2.tgz#9a84ef51b5c11298b5476d6c65b26458c9eae82b" @@ -6302,7 +6871,7 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" -node-notifier@^5.1.2: +node-notifier@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" dependencies: @@ -6311,6 +6880,21 @@ node-notifier@^5.1.2: shellwords "^0.1.1" which "^1.3.0" +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -6384,7 +6968,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -6403,6 +6987,21 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +normalizr@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.2.4.tgz#16aafc540ca99dc1060ceaa1933556322eac4429" + +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + +npm-packlist@^1.1.6: + version "1.1.12" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -6452,6 +7051,14 @@ object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + object-hash@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" @@ -6468,6 +7075,12 @@ object-keys@^1.0.11, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + object.assign@^4.0.4, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" @@ -6500,6 +7113,12 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + object.values@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" @@ -6535,7 +7154,7 @@ onetime@^1.0.0: opn@4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95" + resolved "http://registry.npmjs.org/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95" dependencies: object-assign "^4.0.1" pinkie-promise "^2.0.0" @@ -6706,6 +7325,10 @@ parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" @@ -6865,6 +7488,10 @@ portfinder@^1.0.9: debug "^2.2.0" mkdirp "0.5.x" +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + postcss-calc@^5.2.0: version "5.3.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" @@ -7185,6 +7812,13 @@ pretty-format@^22.1.0: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-format@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + prismjs@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.10.0.tgz#77e5187c2ae6b3253fcc313029cf25fe53778721" @@ -7233,6 +7867,13 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prompts@^0.1.9: + version "0.1.14" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2" + dependencies: + kleur "^2.0.1" + sisteransi "^0.1.1" + prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" @@ -7255,12 +7896,12 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.5.2" -proxy-addr@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" +proxy-addr@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" dependencies: forwarded "~0.1.2" - ipaddr.js "1.6.0" + ipaddr.js "1.8.0" prr@~1.0.1: version "1.0.1" @@ -7322,6 +7963,10 @@ qs@6.5.1, qs@^6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -7431,6 +8076,15 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + rc@^1.1.2: version "1.2.4" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.4.tgz#a0f606caae2a3b862bbd0ef85482c0125b315fa3" @@ -7449,6 +8103,15 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-addons-create-fragment@^15.5.3: version "15.6.2" resolved "https://registry.yarnpkg.com/react-addons-create-fragment/-/react-addons-create-fragment-15.6.2.tgz#a394de7c2c7becd6b5475ba1b97ac472ce7c74f8" @@ -7971,6 +8634,13 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -8019,7 +8689,7 @@ repeat-element@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" -repeat-string@^1.5.2: +repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" @@ -8179,7 +8849,7 @@ resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" -resolve-url@~0.2.1: +resolve-url@^0.2.1, resolve-url@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8250,6 +8920,20 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@5.1.2, safe-buffer@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + sane@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" @@ -8304,7 +8988,7 @@ sass-loader@^6.0.5: lodash.tail "^4.1.1" pify "^3.0.0" -sax@^1.2.1, sax@~1.2.1: +sax@^1.2.1, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -8360,6 +9044,10 @@ semver@^4.0.3: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" +semver@^5.5.0: + version "5.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -8452,6 +9140,24 @@ set-immediate-shim@^1.0.0, set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -8516,6 +9222,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +sisteransi@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -8524,6 +9234,33 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -8584,18 +9321,33 @@ source-map-resolve@^0.3.0: source-map-url "~0.3.0" urix "~0.1.0" +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: source-map "^0.5.6" -source-map-support@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.1.tgz#72291517d1fd0cb9542cee6c27520884b5da1a07" +source-map-support@^0.5.6: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" dependencies: + buffer-from "^1.0.0" source-map "^0.6.0" +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + source-map-url@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" @@ -8661,6 +9413,12 @@ spdy@^3.4.1: select-hose "^2.0.0" spdy-transport "^2.0.18" +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -8701,10 +9459,21 @@ stat-mode@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + "statuses@>= 1.3.1 < 2", statuses@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -8979,10 +9748,18 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" -tapable@^0.2.7, tapable@~0.2.5: +tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" +tapable@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" + +tapable@~0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.9.tgz#af2d8bbc9b04f74ee17af2b4d9048f807acd18a8" + tar-pack@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" @@ -9013,6 +9790,18 @@ tar@^2.0.0, tar@^2.2.1: fstream "^1.0.2" inherits "2" +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -9041,6 +9830,16 @@ test-exclude@^4.1.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" +test-exclude@^4.2.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20" + dependencies: + arrify "^1.0.1" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -9118,6 +9917,28 @@ to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + toggle-selection@^1.0.3: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" @@ -9268,6 +10089,15 @@ underscore@~1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -9311,14 +10141,31 @@ unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + unzip-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + dependencies: + punycode "^2.1.0" + urix@^0.1.0, urix@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -9352,8 +10199,8 @@ url-parse@^1.0.1: requires-port "~1.0.0" url-parse@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.1.tgz#4dec9dad3dc8585f862fed461d2e19bbf623df30" + version "1.4.4" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" dependencies: querystringify "^2.0.0" requires-port "^1.0.0" @@ -9371,6 +10218,10 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + user-home@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" @@ -9381,7 +10232,7 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util.promisify@^1.0.0: +util.promisify@1.0.0, util.promisify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" dependencies: @@ -9554,7 +10405,15 @@ watch@~0.18.0: exec-sh "^0.2.0" minimist "^1.2.0" -watchpack@^1.3.1, watchpack@^1.4.0: +watchpack@^1.3.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + dependencies: + chokidar "^2.0.2" + graceful-fs "^4.1.2" + neo-async "^2.5.0" + +watchpack@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: @@ -9804,9 +10663,13 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + yargs-parser@^4.2.0: version "4.2.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + resolved "http://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" dependencies: camelcase "^3.0.0" @@ -9822,15 +10685,15 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" dependencies: camelcase "^4.1.0" -yargs@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.1.tgz#5fe1ea306985a099b33492001fa19a1e61efe285" +yargs@^11.0.0: + version "11.1.0" + resolved "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -9843,11 +10706,11 @@ yargs@^10.0.3: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^8.1.0" + yargs-parser "^9.0.2" yargs@^6.0.0: version "6.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + resolved "http://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" dependencies: camelcase "^3.0.0" cliui "^3.2.0"