1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch 'master' into removeall-signals

This commit is contained in:
Sonja Happ 2021-02-12 14:10:52 +01:00
commit 6a98a707ec
36 changed files with 1402 additions and 416 deletions

1
.gitignore vendored
View file

@ -17,3 +17,4 @@ yarn-debug.log*
yarn-error.log*
.vscode/
*.code-workspace
package-lock.json

View file

@ -2,6 +2,7 @@ variables:
GIT_SUBMODULE_STRATEGY: normal
DOCKER_TAG: ${CI_COMMIT_BRANCH}
DOCKER_IMAGE: ${CI_REGISTRY_IMAGE}
FF_GITLAB_REGISTRY_HELPER_IMAGE: 1
cache:
untracked: true

625
package-lock.json generated
View file

@ -1109,9 +1109,9 @@
}
},
"@babel/runtime-corejs3": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz",
"integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==",
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz",
"integrity": "sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==",
"requires": {
"core-js-pure": "^3.0.0",
"regenerator-runtime": "^0.13.4"
@ -1165,6 +1165,11 @@
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
},
"@braintree/sanitize-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.0.tgz",
"integrity": "sha512-WmKrB/575EJCzbeSJR3YQ5sET5FaizeljLRw1382qVUeGqzuWBgIS+AF5a0FO51uQTrDpoRgvuHC2IWVsgwkkA=="
},
"@cnakazawa/watch": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz",
@ -1909,6 +1914,19 @@
}
}
},
"@kyleshockey/object-assign-deep": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@kyleshockey/object-assign-deep/-/object-assign-deep-0.4.2.tgz",
"integrity": "sha1-hJAPDu/DcnmPR1G1JigwuCCJIuw="
},
"@kyleshockey/xml": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@kyleshockey/xml/-/xml-1.0.2.tgz",
"integrity": "sha512-iMo32MPLcI9cPxs3YL5kmKxKgDmkSZDCFEqIT5eRk7d/Ll8r4X3SwGYSigzALd6+RHWlFEmjL1QyaQ15xDZFlw==",
"requires": {
"stream": "^0.0.2"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
@ -2269,6 +2287,14 @@
"@types/node": "*"
}
},
"@types/hast": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz",
"integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==",
"requires": {
"@types/unist": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@ -2410,6 +2436,11 @@
"source-map": "^0.6.1"
}
},
"@types/unist": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
"integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
},
"@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
@ -3108,6 +3139,14 @@
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"autolinker": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.14.2.tgz",
"integrity": "sha512-VO66nXUCZFxTq7fVHAaiAkZNXRQ1l3IFi6D5P7DLoyIEAn2E8g7TWbyEgLlz1uW74LfWmu1A17IPWuPQyGuNVg==",
"requires": {
"tslib": "^1.9.3"
}
},
"autoprefixer": {
"version": "9.8.6",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz",
@ -3849,6 +3888,11 @@
"node-int64": "^0.4.0"
}
},
"btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
},
"buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
@ -4114,6 +4158,21 @@
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="
},
"character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="
},
"character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="
},
"character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="
},
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@ -4219,6 +4278,17 @@
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
},
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -4339,6 +4409,11 @@
"delayed-stream": "~1.0.0"
}
},
"comma-separated-tokens": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@ -4520,6 +4595,14 @@
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
},
"copy-to-clipboard": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
"requires": {
"toggle-selection": "^1.0.6"
}
},
"core-js": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
@ -4604,6 +4687,30 @@
"sha.js": "^2.4.8"
}
},
"create-react-class": {
"version": "15.7.0",
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
"requires": {
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
},
"cross-fetch": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
"integrity": "sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==",
"requires": {
"node-fetch": "2.6.1"
},
"dependencies": {
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
}
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@ -4769,6 +4876,11 @@
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
"integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ=="
},
"css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s="
},
"cssdb": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz",
@ -5245,6 +5357,12 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -5464,6 +5582,11 @@
"domelementtype": "1"
}
},
"dompurify": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.6.tgz",
"integrity": "sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ=="
},
"domutils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
@ -5561,6 +5684,11 @@
}
}
},
"emitter-component": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz",
"integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY="
},
"emittery": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz",
@ -5583,7 +5711,7 @@
},
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"resolved": false,
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"requires": {
"iconv-lite": "~0.4.13"
@ -5718,6 +5846,17 @@
"ext": "^1.1.2"
}
},
"es6-weak-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
"integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"requires": {
"d": "1",
"es5-ext": "^0.10.46",
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -6390,6 +6529,15 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
"requires": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -6710,6 +6858,11 @@
"picomatch": "^2.2.1"
}
},
"fast-json-patch": {
"version": "3.0.0-1",
"resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.0.0-1.tgz",
"integrity": "sha512-6pdFb07cknxvPzCeLsFHStEy+MysPJPgZQ9LbQ/2O67unQF93SNqfdSqnPPl71YMHX+AD8gbl7iuoGFzHEdDuw=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -6738,6 +6891,14 @@
"reusify": "^1.0.4"
}
},
"fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"requires": {
"format": "^0.2.0"
}
},
"faye-websocket": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@ -7093,6 +7254,11 @@
"mime-types": "^2.1.12"
}
},
"format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs="
},
"formidable": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz",
@ -7385,6 +7551,15 @@
"minimatch": "~3.0.2"
}
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@ -7562,6 +7737,23 @@
"minimalistic-assert": "^1.0.1"
}
},
"hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="
},
"hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"requires": {
"@types/hast": "^2.0.0",
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@ -7572,6 +7764,11 @@
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
"highlight.js": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.5.0.tgz",
"integrity": "sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw=="
},
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
@ -7988,6 +8185,11 @@
"resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz",
"integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A=="
},
"immutable": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
},
"import-cwd": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@ -8168,6 +8370,20 @@
}
}
},
"is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="
},
"is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"requires": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
}
},
"is-arguments": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
@ -8248,6 +8464,11 @@
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
},
"is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@ -8275,6 +8496,15 @@
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz",
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw=="
},
"is-dom": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz",
"integrity": "sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==",
"requires": {
"is-object": "^1.0.1",
"is-window": "^1.0.2"
}
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@ -8313,6 +8543,11 @@
"is-extglob": "^2.1.1"
}
},
"is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
},
"is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
@ -8333,6 +8568,11 @@
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
},
"is-object": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz",
"integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA=="
},
"is-path-cwd": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
@ -8436,6 +8676,11 @@
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
"is-window": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz",
"integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0="
},
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@ -8473,6 +8718,14 @@
"whatwg-fetch": ">=0.10.0"
}
},
"isomorphic-form-data": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz",
"integrity": "sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==",
"requires": {
"form-data": "^2.3.2"
}
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -10309,6 +10562,11 @@
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
"integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ=="
},
"js-file-download": {
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz",
"integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -10737,7 +10995,7 @@
},
"libcimsvg": {
"version": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git#7e5c48fff7eced878da471b5c69ab4a8b575a6c9",
"from": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git"
"from": "libcimsvg@git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git"
},
"lie": {
"version": "3.3.0",
@ -10898,6 +11156,15 @@
"tslib": "^1.10.0"
}
},
"lowlight": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.18.0.tgz",
"integrity": "sha512-Zlc3GqclU71HRw5fTOy00zz5EOlqAdKMYhOFIO8ay4SQEDQgFuhR8JNwDIzAGMLoqTsWxe0elUNmq5o2USRAzw==",
"requires": {
"fault": "^1.0.0",
"highlight.js": "~10.5.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -10906,6 +11173,14 @@
"yallist": "^4.0.0"
}
},
"lru-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
"integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
"requires": {
"es5-ext": "~0.10.2"
}
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
@ -10986,6 +11261,33 @@
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"memoizee": {
"version": "0.4.15",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
"integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==",
"requires": {
"d": "^1.0.1",
"es5-ext": "^0.10.53",
"es6-weak-map": "^2.0.3",
"event-emitter": "^0.3.5",
"is-promise": "^2.2.2",
"lru-queue": "^0.1.0",
"next-tick": "^1.1.0",
"timers-ext": "^0.1.7"
},
"dependencies": {
"is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
},
"next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
}
}
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -11334,6 +11636,11 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"moment-duration-format": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz",
"integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -12211,6 +12518,19 @@
"safe-buffer": "^5.1.1"
}
},
"parse-entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"requires": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
}
},
"parse-json": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
@ -13514,6 +13834,14 @@
}
}
},
"prismjs": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"requires": {
"clipboard": "^2.0.0"
}
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -13570,6 +13898,14 @@
"warning": "^4.0.0"
}
},
"property-information": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
"requires": {
"xtend": "^4.0.0"
}
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
@ -13678,6 +14014,11 @@
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"querystring-browser": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/querystring-browser/-/querystring-browser-1.0.4.tgz",
"integrity": "sha1-8uNYgYQKgZvHsb9Zf68JeeZiLcY="
},
"querystring-es3": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
@ -14045,6 +14386,15 @@
"prop-types": "^15.6.2"
}
},
"react-copy-to-clipboard": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.3.tgz",
"integrity": "sha512-9S3j+m+UxDZOM0Qb8mhnT/rMR0NGSrj9A/073yz2DSxPMYhmYFBMYIdI2X4o8AjOjyFsSNxDRnCX6s/gRxpriw==",
"requires": {
"copy-to-clipboard": "^3",
"prop-types": "^15.5.8"
}
},
"react-d3": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/react-d3/-/react-d3-0.4.0.tgz",
@ -14054,6 +14404,15 @@
"react": ">0.12.0"
}
},
"react-debounce-input": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.2.3.tgz",
"integrity": "sha512-7Bfjm9sxrtgB+IsSrdXoo4CVqKg7CbWC68dNhr8q7ZmY6C0AqtR524//SenHQWT+eeSG9DmSLWNWCUFSyaaWSQ==",
"requires": {
"lodash.debounce": "^4",
"prop-types": "^15.7.2"
}
},
"react-dev-utils": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.1.tgz",
@ -14238,6 +14597,29 @@
"prop-types": "^15.7.2"
}
},
"react-immutable-proptypes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz",
"integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==",
"requires": {
"invariant": "^2.2.2"
}
},
"react-immutable-pure-component": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-1.2.4.tgz",
"integrity": "sha512-zPXaFWxaF4+ztVMFNMlCFkrhjpb9MPcL3JnXUpb6wKGF1+vBoSgClFbpbOsZAji7gm+RHBE24H44Lday2xxPjw=="
},
"react-inspector": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-2.3.1.tgz",
"integrity": "sha512-tUUK7t3KWgZEIUktOYko5Ic/oYwvjEvQUFAGC1UeMeDaQ5za2yZFtItJa2RTwBJB//NxPr000WQK6sEbqC6y0Q==",
"requires": {
"babel-runtime": "^6.26.0",
"is-dom": "^1.0.9",
"prop-types": "^15.6.1"
}
},
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
@ -14259,6 +14641,23 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-motion": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz",
"integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==",
"requires": {
"performance-now": "^0.2.0",
"prop-types": "^15.5.8",
"raf": "^3.1.0"
},
"dependencies": {
"performance-now": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
"integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU="
}
}
},
"react-notification-system": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/react-notification-system/-/react-notification-system-0.4.0.tgz",
@ -14427,6 +14826,18 @@
"transformation-matrix": "^2.2.0"
}
},
"react-syntax-highlighter": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.4.3.tgz",
"integrity": "sha512-TnhGgZKXr5o8a63uYdRTzeb8ijJOgRGe0qjrE0eK/gajtdyqnSO6LqB3vW16hHB0cFierYSoy/AOJw8z1Dui8g==",
"requires": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"lowlight": "^1.17.0",
"prismjs": "^1.22.0",
"refractor": "^3.2.0"
}
},
"react-textarea-autosize": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz",
@ -14591,6 +15002,24 @@
"symbol-observable": "^1.2.0"
}
},
"redux-immutable": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-3.1.0.tgz",
"integrity": "sha1-yvvWhuBxEmERm5wolgk13EeknQo=",
"requires": {
"immutable": "^3.8.1"
}
},
"refractor": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.3.1.tgz",
"integrity": "sha512-vaN6R56kLMuBszHSWlwTpcZ8KTMG6aUCok4GrxYDT20UIOXxOc5o6oDc8tNTzSlH3m2sI+Eu9Jo2kVdDcUTWYw==",
"requires": {
"hastscript": "^6.0.0",
"parse-entities": "^2.0.0",
"prismjs": "~1.23.0"
}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -14718,6 +15147,15 @@
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
},
"remarkable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz",
"integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==",
"requires": {
"argparse": "^1.0.10",
"autolinker": "^3.11.0"
}
},
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@ -14880,6 +15318,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"reselect": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@ -15452,6 +15895,12 @@
}
}
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@ -15517,6 +15966,11 @@
}
}
},
"serialize-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
"integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go="
},
"serialize-javascript": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
@ -15949,6 +16403,11 @@
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="
},
"spdx-correct": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
@ -16113,6 +16572,14 @@
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"stream": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz",
"integrity": "sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=",
"requires": {
"emitter-component": "^1.1.1"
}
},
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@ -16439,6 +16906,116 @@
"util.promisify": "~1.0.0"
}
},
"swagger-client": {
"version": "3.12.2",
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.12.2.tgz",
"integrity": "sha512-l4aAty0VNmHaOEEvOI7Tc6xtUZLNxtFEOZzansjxHjbV9o2ZsiFPMdomIGj14n/Zb0S2eN83cEJYHJ3SENUYPw==",
"requires": {
"@babel/runtime-corejs3": "^7.11.2",
"btoa": "^1.2.1",
"buffer": "^6.0.3",
"cookie": "~0.4.1",
"cross-fetch": "^3.0.6",
"deep-extend": "~0.6.0",
"fast-json-patch": "^3.0.0-1",
"isomorphic-form-data": "~2.0.0",
"js-yaml": "^3.14.0",
"lodash": "^4.17.19",
"qs": "^6.9.4",
"querystring-browser": "^1.0.4",
"traverse": "~0.6.6",
"url": "~0.11.0"
},
"dependencies": {
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"qs": {
"version": "6.9.6",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
"integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ=="
}
}
},
"swagger-ui-react": {
"version": "3.42.0",
"resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-3.42.0.tgz",
"integrity": "sha512-uxoM5UxmjSIcOsAvvD2yKirRR4tCqJMiEklDMgg68JuoFsrpKk9RlUm5A2rI2dA8HdpBO1dIUA8zHdIdpMF5aA==",
"requires": {
"@babel/runtime-corejs3": "^7.12.13",
"@braintree/sanitize-url": "^5.0.0",
"@kyleshockey/object-assign-deep": "^0.4.2",
"@kyleshockey/xml": "^1.0.2",
"base64-js": "^1.5.1",
"classnames": "^2.2.6",
"css.escape": "1.5.1",
"deep-extend": "0.6.0",
"dompurify": "^2.2.6",
"ieee754": "^1.2.1",
"immutable": "^3.x.x",
"js-file-download": "^0.4.12",
"js-yaml": "^3.13.1",
"lodash": "^4.17.20",
"memoizee": "^0.4.15",
"prop-types": "^15.7.2",
"randombytes": "^2.1.0",
"react-copy-to-clipboard": "5.0.3",
"react-debounce-input": "^3.2.3",
"react-immutable-proptypes": "2.2.0",
"react-immutable-pure-component": "^1.1.1",
"react-inspector": "^2.3.0",
"react-motion": "^0.5.2",
"react-redux": "=4.4.10",
"react-syntax-highlighter": "^15.4.3",
"redux": "=3.7.2",
"redux-immutable": "3.1.0",
"remarkable": "^2.0.1",
"reselect": "^4.0.0",
"serialize-error": "^2.1.0",
"sha.js": "^2.4.11",
"swagger-client": "^3.12.2",
"url-parse": "^1.4.7",
"xml-but-prettier": "^1.0.1",
"zenscroll": "^4.0.2"
},
"dependencies": {
"react-redux": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.10.tgz",
"integrity": "sha512-tjL0Bmpkj75Td0k+lXlF8Fc8a9GuXFv/3ahUOCXExWs/jhsKiQeTffdH0j5byejCGCRL4tvGFYlrwBF1X/Aujg==",
"requires": {
"create-react-class": "^15.5.1",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.0.0",
"lodash": "^4.17.11",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2"
}
},
"redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
"requires": {
"lodash": "^4.2.1",
"lodash-es": "^4.2.1",
"loose-envify": "^1.1.0",
"symbol-observable": "^1.0.3"
}
}
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
@ -16663,11 +17240,26 @@
"setimmediate": "^1.0.4"
}
},
"timers-ext": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
"integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
"requires": {
"es5-ext": "~0.10.46",
"next-tick": "1"
}
},
"timsort": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"tiny-invariant": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
@ -16735,6 +17327,11 @@
"is-number": "^7.0.0"
}
},
"toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@ -16763,6 +17360,11 @@
"resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.5.0.tgz",
"integrity": "sha512-Z9UotSHOWMcqYYKwQUts49CjCH8H51HBbPN4uiQIwNpGMZs70LpugThBmLpUy2LFdrbfXGhdlu4WJ3yILK6kcw=="
},
"traverse": {
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
"integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc="
},
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@ -18827,6 +19429,14 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ=="
},
"xml-but-prettier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz",
"integrity": "sha1-9aMyZ+1CzNTjVcYlV6XjmwH7QPM=",
"requires": {
"repeat-string": "^1.5.2"
}
},
"xml-name-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
@ -18957,6 +19567,11 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
"zenscroll": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz",
"integrity": "sha1-6NV3TRwHOKR7z6hynzcS4t7d6yU="
}
}
}

View file

@ -30,6 +30,7 @@
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
"lodash": "^4.17.20",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"multiselect-react-dropdown": "^1.6.2",
"node-sass": "^4.14.1",
"popper.js": "^1.16.1",
@ -57,6 +58,7 @@
"react-trafficlight": "^5.2.1",
"sass": "^1.29.0",
"superagent": "^6.1.0",
"swagger-ui-react": "^3.42.0",
"ts-node": "^9.0.0",
"type-fest": "^0.13.1",
"typescript": "^4.1.2",

View file

@ -38,6 +38,7 @@ import Scenarios from './scenario/scenarios';
import Scenario from './scenario/scenario';
import Users from './user/users';
import User from './user/user';
import APIBrowser from './common/api-browser';
import './styles/app.css';
@ -117,6 +118,7 @@ class App extends React.Component {
<Route path="/infrastructure" component={InfrastructureComponents} />
<Route path="/account" component={User} />
<Route path="/users" component={Users} />
<Route path="/api" component={APIBrowser} />
</div>
</div>

82
src/common/api-browser.js Normal file
View file

@ -0,0 +1,82 @@
/**
* This file is part of VILLASweb.
*
* VILLASweb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VILLASweb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React from 'react';
import SwaggerUI from 'swagger-ui-react'
import 'swagger-ui-react/swagger-ui.css'
import '../styles/swagger-ui.css';
import RestAPI from './api/rest-api';
import RestDataManager from './data-managers/rest-data-manager';
class APIBrowser extends React.Component {
constructor(props) {
super(props);
this.state = {
'spec': null
};
}
mangleSpec(spec) {
spec.host = window.location.host;
return spec;
}
componentWillMount() {
this._asyncRequest = RestAPI.get('/api/v2/openapi')
.then((spec) => {
this._asyncRequest = null;
this.setState({
'spec': this.mangleSpec(spec)
});
});
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
requestInterceptor(req) {
var token = localStorage.getItem('token');
if (token)
req.headers.Authorization = 'Bearer ' + token;
return req
}
render() {
return (
<div>
{ this.state.spec &&
<SwaggerUI
spec={this.state.spec}
tryItOutEnabled={true}
requestInterceptor={this.requestInterceptor}
/> }
</div>
);
}
}
export default APIBrowser;

View file

@ -26,10 +26,13 @@ import NotificationsFactory from "../data-managers/notifications-factory";
function isNetworkError(err, url) {
let result = false;
// If not status nor response fields, it is a network error. TODO: Handle timeouts
if (err.status == null || err.status === 500 || err.response == null) {
result = true;
if (err.status === 500 && err.response != null){
let notification = NotificationsFactory.INTERNAL_SERVER_ERROR(err.response)
NotificationsDataManager.addNotification(notification);
} else if (err.status == null || err.status === 500 || err.response == null) {
// If not status nor response fields, it is a network error. TODO: Handle timeouts
result = true;
let notification = err.timeout? NotificationsFactory.REQUEST_TIMEOUT : NotificationsFactory.SERVER_NOT_REACHABLE(url);
NotificationsDataManager.addNotification(notification);
}

View file

@ -42,6 +42,14 @@ class NotificationsFactory {
};
}
static INTERNAL_SERVER_ERROR(response) {
return {
title: 'Internal server error',
message: response.message,
level: 'error'
};
}
static ADD_ERROR(message) {
return {
title: "Add Error",

View file

@ -37,7 +37,7 @@ class DeleteDialog extends React.Component {
<Modal.Body>
Are you sure you want to delete the {this.props.title} <strong>'{this.props.name}'</strong>?
<Collapse isOpened={this.props.managedexternally} >
<FormLabel size="sm">The IC will be deleted if the respective VILLAScontroller sends "gone" state and no component config is using the IC anymore</FormLabel>
<FormLabel size="sm">The IC will be deleted if the respective manager sends "gone" state and no component config is using the IC anymore</FormLabel>
</Collapse>
</Modal.Body>

View file

@ -18,28 +18,10 @@
import React from 'react';
import config from '../config';
import {Redirect} from "react-router-dom";
import { Redirect } from "react-router-dom";
import { NavLink } from 'react-router-dom';
class Home extends React.Component {
constructor(props) {
super(props);
// create url for API documentation, distinguish between localhost and production deployment
let docs_url = "";
let docs_location = "/swagger/index.html";
let base_url = window.location.origin;
if (base_url.search("localhost") === -1){
docs_url = base_url + docs_location;
} else {
// useful for local testing, replace port 3000 with port 4000 (port of backend)
docs_url = base_url.replace("3000", "4000") + docs_location;
}
this.state = {
docs_url: docs_url
};
}
getCounts(type) {
if (this.state.hasOwnProperty('counts'))
@ -68,10 +50,9 @@ class Home extends React.Component {
</p>
<p>
An interactive documentation of the VILLASweb API is available <a href={this.state.docs_url} target="_blank" rel="noopener noreferrer">here</a>.
An interactive documentation of the VILLASweb API is available <NavLink to="/api">here</NavLink>.
</p>
<h3>Data Model</h3>
<img height={400} src={require('../img/datamodel.png').default} alt="Datamodel VILLASweb" />
@ -103,8 +84,6 @@ class Home extends React.Component {
<li>Users can have access to multiple scenarios</li>
</ul>
<h3>Credits</h3>
<p>VILLASweb is developed by the <a href="http://acs.eonerc.rwth-aachen.de">Institute for Automation of Complex Power Systems</a> at the <a href="https;//www.rwth-aachen.de">RWTH Aachen University</a>.</p>
<ul>

View file

@ -33,6 +33,7 @@ class SidebarMenu extends React.Component {
}
<li><NavLink to="/account" title="Account">Account</NavLink></li>
<li><NavLink to="/logout" title="Logout">Logout</NavLink></li>
<li><NavLink to="/api" title="API Browser">API Browser</NavLink></li>
</ul>
</div>
);

View file

@ -35,6 +35,7 @@ class TableColumn extends Component {
labelKey: null,
checkbox: false,
checkboxKey: '',
checkboxDisabled: null,
labelStyle: null,
labelModifier: null

View file

@ -59,7 +59,7 @@ class CustomTable extends Component {
const modifier = child.props.modifier;
if (modifier && content != null) {
content = modifier(content);
content = modifier(content, data);
}
let cell = [];
@ -72,7 +72,7 @@ class CustomTable extends Component {
cell.push(<Link to={child.props.link + data[linkKey]}>{content}</Link>);
} else if (child.props.clickable) {
cell.push(<Button variant="link" onClick={() => child.props.onClick(index)}>{content}</Button>);
} else if (linkKey == 'filebuttons') {
} else if (linkKey === 'filebuttons') {
content.forEach(element => {
cell.push(<OverlayTrigger key={element} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"export"}`}>Download {element}</Tooltip>} >
<Button variant='table-control-button' onClick={() => child.props.onDownload(element)} disabled={child.props.onDownload == null}>{element + ' ' }
@ -117,8 +117,18 @@ class CustomTable extends Component {
if (child.props.checkbox) {
const checkboxKey = child.props.checkboxKey;
cell.push(<FormCheck className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
let isDisabled = false;
if (child.props.checkboxDisabled != null){
isDisabled = !child.props.checkboxDisabled(index)
}
cell.push(
<FormCheck
className="table-control-checkbox"
inline
disabled = {isDisabled}
checked={checkboxKey ? data[checkboxKey] : null}
onChange={e => child.props.onChecked(data, e)}
/>);
}
if (child.props.exportButton) {

View file

@ -122,8 +122,8 @@ class EditICDialog extends React.Component {
case "simulator":
typeOptions = ["dummy","generic","dpsim","rtlab","rscad", "opalrt"];
break;
case "controller":
typeOptions = ["kubernetes","villas-controller"];
case "manager":
typeOptions = ["villas-node","villas-relay","generic"];
break;
case "gateway":
typeOptions = ["villas-node","villas-relay"];
@ -158,10 +158,10 @@ class EditICDialog extends React.Component {
<FormLabel column={false}>Category</FormLabel>
<FormControl as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
<option>simulator</option>
<option>controller</option>
<option>service</option>
<option>gateway</option>
<option>equipment</option>
<option>manager</option>
</FormControl>
</FormGroup>
<FormGroup controlId="type">

View file

@ -16,75 +16,93 @@
******************************************************************************/
import React from 'react';
import { Button, ButtonToolbar, DropdownButton, Dropdown } from 'react-bootstrap';
import TimePicker from 'react-bootstrap-time-picker'
import { Button, ButtonToolbar, DropdownButton, Dropdown, InputGroup, FormControl } from 'react-bootstrap';
class ICAction extends React.Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {
selectedAction: null,
selectedDelay: 0
};
let t = new Date()
Number.prototype.pad = function(size) {
var s = String(this);
while (s.length < (size || 2)) {s = "0" + s;}
return s;
}
static getDerivedStateFromProps(props, state){
if (state.selectedAction == null) {
if (props.actions != null && props.actions.length > 0) {
return{
selectedAction: props.actions[0]
};
}
}
return null
}
let time = new Date();
time.setMinutes(5 * Math.round(time.getMinutes() / 5 + 1))
setAction = id => {
// search action
for (let action of this.props.actions) {
if (action.id === id) {
this.setState({ selectedAction: action });
}
}
this.state = {
selectedAction: null,
time: time
};
}
setDelayForAction = time => {
// time in int format: (hours * 3600 + minutes * 60 + seconds)
this.setState({selectedDelay: time})
static getDerivedStateFromProps(props, state) {
if (state.selectedAction == null) {
if (props.actions != null && props.actions.length > 0) {
return {
selectedAction: props.actions[0]
};
}
}
return null
}
render() {
let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1"
const actionList = this.props.actions.map(action => (
<Dropdown.Item key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
{action.title}
</Dropdown.Item>
));
return <div>
{"Select delay for command execution (Format hh:mm, max 1h):"}
<TimePicker
format={24}
initialValue={this.state.selectedDelay}
value={this.state.selectedDelay}
start={"00:00"}
end={"01:00"}
step={1}
onChange={this.setDelayForAction}
/>
<ButtonToolbar>
<DropdownButton title={this.state.selectedAction != null ? this.state.selectedAction.title : ''} id="action-dropdown" onSelect={this.setAction}>
{actionList}
</DropdownButton>
<Button style={{ marginLeft: '5px' }} disabled={sendCommandDisabled} onClick={() => this.props.runAction(this.state.selectedAction, this.state.selectedDelay)}>Send command</Button>
</ButtonToolbar>
</div>;
setAction = id => {
// search action
for (let action of this.props.actions) {
if (action.id === id) {
this.setState({ selectedAction: action });
}
}
};
setTimeForAction = (time) => {
this.setState({ time: new Date(time) })
}
render() {
let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1"
let time = this.state.time.getFullYear().pad(4) + '-' +
this.state.time.getMonth().pad(2) + '-' +
this.state.time.getDay().pad(2) + 'T' +
this.state.time.getHours().pad(2) + ':' +
this.state.time.getMinutes().pad(2);
const actionList = this.props.actions.map(action => (
<Dropdown.Item key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
{action.title}
</Dropdown.Item>
));
return <div>
<InputGroup>
<InputGroup.Prepend>
<DropdownButton
variant="outline-secondary"
title={this.state.selectedAction != null ? this.state.selectedAction.title : ''}
id="action-dropdown"
onSelect={this.setAction}>
{actionList}
</DropdownButton>
<FormControl
type="datetime-local"
variant="outline-secondary"
value={time}
onChange={this.setTimeForAction} />
</InputGroup.Prepend>
<Button
variant="outline-secondary"
disabled={sendCommandDisabled}
onClick={() => this.props.runAction(this.state.selectedAction, this.state.time)}>Run</Button>
</InputGroup>
<small className="text-muted">Select time for synced command execution</small>
</div>;
}
}
export default ICAction;

View file

@ -68,8 +68,28 @@ class ICDialog extends React.Component {
<form>
<Row>
<Col>
<h5>Status:</h5>
<h5>State: {this.props.ic.state}</h5>
<h5>Category: {this.props.ic.category}</h5>
<h5>Type: {this.props.ic.type}</h5>
<h5>Uptime: {this.props.ic.uptime}</h5>
<h5>Location: {this.props.ic.location}</h5>
<h5>Description: {this.props.ic.description}</h5>
<h5>Websocket URL: {this.props.ic.websocketurl}</h5>
<h5>API URL: {this.props.ic.apiurl}</h5>
<h5>Start parameter scheme:</h5>
<ReactJson
src={this.props.ic.startParameterScheme}
name={false}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={false}
collapsed={0}
/>
</Col>
<Col>
<h5>Raw Status:</h5>
<ReactJson
src={this.props.ic.statusupdateraw}
name={false}
@ -79,37 +99,36 @@ class ICDialog extends React.Component {
collapsed={1}
/>
</Col>
{this.props.ic.type === "villas-node" || this.props.ic.type === "villas-relay" ? (
<>
<div className='section-buttons-group-right'>
<Button style={{margin: '5px'}} size='sm' onClick={() => this.downloadGraph(graphURL)}><Icon
icon="download"/></Button>
</div>
<h5>Graph:</h5>
<div>
<img alt={"Graph image download failed and/or incorrect image API URL"} src={graphURL}/>
</div>
{this.props.ic.type === "villas-node" || this.props.ic.type === "villas-relay" ? (
<Col>
<div className='section-buttons-group-right'>
<Button style={{margin: '5px'}} size='sm' onClick={() => this.downloadGraph(graphURL)}><Icon
icon="download"/></Button>
</div>
<h5>Graph:</h5>
<div>
<img alt={"Graph image download failed and/or incorrect image URL"} src={graphURL}/>
</div>
{this.props.userRole === "Admin" ? (
<div>
<h5>Controls:</h5>
{this.props.userRole === "Admin" ? (
<div>
<Button style={{margin: '5px'}} size='lg'
onClick={() => this.setState({confirmCommand: true, command: 'restart'})}>Restart</Button>
<Button style={{margin: '5px'}} size='lg' onClick={() => this.setState({
confirmCommand: true,
command: 'shutdown'
})}>Shutdown</Button>
</div>
</div>)
: (<div/>)}
<h5>Controls:</h5>
<div>
<Button style={{margin: '5px'}} size='lg'
onClick={() => this.setState({confirmCommand: true, command: 'restart'})}>Restart</Button>
<Button style={{margin: '5px'}} size='lg' onClick={() => this.setState({
confirmCommand: true,
command: 'shutdown'
})}>Shutdown</Button>
</div>
</div>)
: (<div/>)}
<ConfirmCommand show={this.state.confirmCommand} command={this.state.command} name={this.props.ic.name}
onClose={c => this.closeConfirmModal(c)}/>
</Col>
): (<div/>)}
<ConfirmCommand show={this.state.confirmCommand} command={this.state.command} name={this.props.ic.name}
onClose={c => this.closeConfirmModal(c)}/>
</>) : (<div/>)}
</Col>
</Row>
</form>
</Dialog>

View file

@ -84,6 +84,7 @@ class InfrastructureComponentStore extends ArrayStore {
let tempIC = action.ic;
if(!tempIC.managedexternally){
tempIC.state = action.data.state;
tempIC.uptime = action.data.time_now - action.data.time_started;
tempIC.statusupdateraw = action.data;
AppDispatcher.dispatch({
type: 'ics/start-edit',

View file

@ -24,8 +24,14 @@ class IcsDataManager extends RestDataManager {
super('ic', '/ic');
}
doActions(ic, action, token = null) {
RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), action, token).then(response => {
doActions(ic, actions, token = null) {
for (let action of actions) {
if (action.when)
// Send timestamp as Unix Timestamp
action.when = Math.round(action.when.getTime() / 1000);
}
RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), actions, token).then(response => {
AppDispatcher.dispatch({
type: 'ics/action-started',
data: response

View file

@ -17,11 +17,12 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button, Badge } from 'react-bootstrap';
import {Button, Badge, Tooltip, OverlayTrigger} from 'react-bootstrap';
import FileSaver from 'file-saver';
import _ from 'lodash';
import moment from 'moment'
import AppDispatcher from '../common/app-dispatcher';
import InfrastructureComponentStore from './ic-store';
@ -74,9 +75,27 @@ class InfrastructureComponents extends Component {
}
});
// collect number of external ICs
let externalICs = ics.filter(ic => ic.managedexternally === true)
let numberOfExternalICs = externalICs.length;
// collect all IC categories
let managers = ics.filter(ic => ic.category === "manager")
let gateways = ics.filter(ic => ic.category === "gateway")
let simulators = ics.filter(ic => ic.category === "simulator")
let services = ics.filter(ic => ic.category === "service")
let equipment = ics.filter(ic => ic.category === "equipment")
return {
sessionToken: localStorage.getItem("token"),
ics: ics,
managers: managers,
gateways: gateways,
simulators: simulators,
services: services,
equipment: equipment,
numberOfExternalICs,
modalIC: {},
deleteModal: false,
icModal: false,
@ -126,7 +145,6 @@ class InfrastructureComponents extends Component {
}
}
closeNewModal(data) {
this.setState({ newModal : false });
@ -195,7 +213,9 @@ class InfrastructureComponents extends Component {
}
}
onICChecked(index, event) {
onICChecked(ic, event) {
let index = this.state.ics.indexOf(ic);
const selectedICs = Object.assign([], this.state.selectedICs);
for (let key in selectedICs) {
if (selectedICs[key] === index) {
@ -220,8 +240,10 @@ class InfrastructureComponents extends Component {
this.setState({ selectedICs: selectedICs });
}
runAction(action) {
runAction(action, when) {
for (let index of this.state.selectedICs) {
action.when = when;
AppDispatcher.dispatch({
type: 'ics/start-action',
ic: this.state.ics[index],
@ -241,7 +263,6 @@ class InfrastructureComponents extends Component {
}
stateLabelStyle(state, component){
var style = [ 'badge' ];
if (InfrastructureComponents.isICOutdated(component) && state !== 'shutdown') {
@ -286,7 +307,6 @@ class InfrastructureComponents extends Component {
default:
style.push('badge-default');
/* Possible states of ICs
* 'error': ['resetting', 'error'],
'idle': ['resetting', 'error', 'idle', 'starting', 'shuttingdown'],
@ -305,50 +325,44 @@ class InfrastructureComponents extends Component {
return style.join(' ')
}
stateUpdateModifier(updatedAt) {
stateUpdateModifier(updatedAt, component) {
let dateFormat = 'ddd, DD MMM YYYY HH:mm:ss ZZ';
let dateTime = moment(updatedAt, dateFormat);
return dateTime.fromNow()
}
modifyManagedExternallyColumn(managedExternally){
modifyManagedExternallyColumn(managedExternally, component){
if(managedExternally){
return <Icon icon='check' />
} else {
return ""
}
}
modifyUptimeColumn(uptime){
modifyUptimeColumn(uptime, component){
if(uptime >= 0){
return <span>{uptime + "s"}</span>
let momentDurationFormatSetup = require("moment-duration-format");
momentDurationFormatSetup(moment)
let timeString = moment.duration(uptime, "seconds").humanize();
return <span>{timeString}</span>
}
else{
return <Badge variant="secondary">Unknown</Badge>
}
}
modifyNameColumn(name){
let ic = this.state.ics.find(ic => ic.name === name);
if(ic.type === "villas-node" || ic.type === "villas-relay" || ic.managedexternally){
return <Button variant="link" onClick={() => this.openICStatus(ic)}>{name}</Button> }
else{
return <span>{name}</span>
}
modifyNameColumn(name, component){
let index = this.state.ics.indexOf(component);
return <Button variant="link" style={{color: '#047cab'}} onClick={() => this.openICStatus(component)}>{name}</Button>
}
openICStatus(ic){
let index = this.state.ics.indexOf(ic);
this.setState({ icModal: true, modalIC: ic, modalIndex: index })
}
sendControlCommand(command,ic){
if(command === "restart"){
AppDispatcher.dispatch({
type: 'ics/restart',
@ -362,79 +376,140 @@ class InfrastructureComponents extends Component {
token: this.state.sessionToken,
});
}
}
isExternalIC(index){
let ic = this.state.ics[index]
return ic.managedexternally
}
getICCategoryTable(ics, editable, title){
if (ics && ics.length > 0) {
return (<div>
<h2>{title}</h2>
<Table data={ics}>
<TableColumn
checkbox
checkboxDisabled={(index) => this.isExternalIC(index)}
onChecked={(ic, event) => this.onICChecked(ic, event)}
width='30'
/>
<TableColumn
title='Name'
dataKeys={['name']}
modifier={(name, component) => this.modifyNameColumn(name, component)}
/>
<TableColumn
title='State'
labelKey='state'
tooltipKey='error'
labelStyle={(state, component) => this.stateLabelStyle(state, component)}
/>
<TableColumn
title='Type'
dataKeys={['type']}
/>
<TableColumn
title='Uptime'
dataKey='uptime'
modifier={(uptime, component) => this.modifyUptimeColumn(uptime, component)}
/>
<TableColumn
title='Last Update'
dataKey='stateUpdateAt'
modifier={(stateUpdateAt, component) => this.stateUpdateModifier(stateUpdateAt, component)}
/>
{this.state.currentUser.role === "Admin" && editable ?
<TableColumn
width='200'
editButton
exportButton
deleteButton
onEdit={index => this.setState({editModal: true, modalIC: ics[index], modalIndex: index})}
onExport={index => this.exportIC(index)}
onDelete={index => this.setState({deleteModal: true, modalIC: ics[index], modalIndex: index})}
/>
:
<TableColumn
width='100'
exportButton
onExport={index => this.exportIC(index)}
/>
}
</Table>
</div>);
} else {
return <div/>
}
}
render() {
const buttonStyle = {
marginLeft: '10px'
};
let managerTable = this.getICCategoryTable(this.state.managers, false, "IC Managers")
let simulatorTable = this.getICCategoryTable(this.state.simulators, true, "Simulators")
let gatewayTable = this.getICCategoryTable(this.state.gateways, true, "Gateways")
let serviceTable = this.getICCategoryTable(this.state.services, true, "Services")
let equipmentTable = this.getICCategoryTable(this.state.equipment, true, "Equipment")
return (
<div className='section'>
<h1>Infrastructure Components</h1>
<Table data={this.state.ics}>
<TableColumn checkbox onChecked={(index, event) => this.onICChecked(index, event)} width='30' />
<TableColumn title='Name' dataKeys={['name', 'rawProperties.name']} modifier={(name) => this.modifyNameColumn(name)}/>
<TableColumn title='State' labelKey='state' tooltipKey='error' labelStyle={(state, component) => this.stateLabelStyle(state, component)} />
<TableColumn title='Category' dataKeys={['category', 'rawProperties.category']} />
<TableColumn title='Type' dataKeys={['type', 'rawProperties.type']} />
<TableColumn title='Managed externally' dataKey='managedexternally' modifier={(managedexternally) => this.modifyManagedExternallyColumn(managedexternally)} width='105' />
<TableColumn title='Uptime' dataKey='uptime' modifier={(uptime) => this.modifyUptimeColumn(uptime)}/>
<TableColumn title='Location' dataKey='location' />
{/* <TableColumn title='Realm' dataKeys={['properties.realm', 'rawProperties.realm']} /> */}
<TableColumn title='WebSocket URL' dataKey='websocketurl' />
<TableColumn title='API URL' dataKey='apiurl' />
<TableColumn title='Last Update' dataKey='stateUpdateAt' modifier={(stateUpdateAt) => this.stateUpdateModifier(stateUpdateAt)} />
<h1>Infrastructure Components
{this.state.currentUser.role === "Admin" ?
<TableColumn
width='200'
editButton
exportButton
deleteButton
onEdit={index => this.setState({ editModal: true, modalIC: this.state.ics[index], modalIndex: index })}
onExport={index => this.exportIC(index)}
onDelete={index => this.setState({ deleteModal: true, modalIC: this.state.ics[index], modalIndex: index })}
/>
:
<TableColumn
width='100'
exportButton
onExport={index => this.exportIC(index)}
/>
(<span>
<OverlayTrigger
key={1}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"add"}`}> Add Infrastructure Component </Tooltip>} >
<Button onClick={() => this.setState({newModal: true})} style={buttonStyle}><Icon icon="plus"
/></Button>
</OverlayTrigger>
<OverlayTrigger
key={2}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"import"}`}> Import Infrastructure Component </Tooltip>} >
<Button onClick={() => this.setState({importModal: true})} style={buttonStyle}><Icon icon="upload"
/></Button>
</OverlayTrigger>
</span>)
:
(<span> </span>)
}
</Table>
{this.state.currentUser.role === "Admin" ?
<div style={{ float: 'left' }}>
</h1>
{managerTable}
{simulatorTable}
{gatewayTable}
{serviceTable}
{equipmentTable}
{this.state.currentUser.role === "Admin" && this.state.numberOfExternalICs > 0 ?
<div style={{float: 'left'}}>
<ICAction
runDisabled={this.state.selectedICs.length === 0}
runAction={action => this.runAction(action)}
runAction={(action, when) => this.runAction(action, when)}
actions={[
{ id: '-1', title: 'Select command', data: { action: 'none' } },
{ id: '0', title: 'Reset', data: { action: 'reset' } },
{ id: '1', title: 'Shutdown', data: { action: 'shutdown' } },
]}
{id: '-1', title: 'Action', data: {action: 'none'}},
{id: '0', title: 'Reset', data: {action: 'reset'}},
{id: '1', title: 'Shutdown', data: {action: 'shutdown'}},
]}
/>
</div>
:
<div> </div>
}
{this.state.currentUser.role === "Admin" ?
<div style={{ float: 'right' }}>
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Infrastructure Component</Button>
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
</div>
:
<div> </div>
<div/>
}
<div style={{ clear: 'both' }} />
<NewICDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} />
<NewICDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} managers={this.state.managers} />
<EditICDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} ic={this.state.modalIC} />
<ImportICDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
<DeleteDialog title="infrastructure-component" name={this.state.modalIC.name || 'Unknown'} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
<ICDialog
show={this.state.icModal}
onClose={data => this.closeICModal(data)}
@ -443,7 +518,6 @@ class InfrastructureComponents extends Component {
userRole={this.state.currentUser.role}
sendControlCommand={(command, ic) => this.sendControlCommand(command, ic)}/>
<DeleteDialog title="infrastructure-component" name={this.state.modalIC.name || 'Unknown'} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
</div>
);
}

View file

@ -34,7 +34,8 @@ class NewICDialog extends React.Component {
category: '',
managedexternally: false,
description: '',
location: ''
location: '',
manager: ''
};
}
@ -48,7 +49,8 @@ class NewICDialog extends React.Component {
uuid: this.state.uuid,
managedexternally: this.state.managedexternally,
location: this.state.location,
description: this.state.description
description: this.state.description,
manager: this.state.manager
};
if (this.state.websocketurl != null && this.state.websocketurl !== "" && this.state.websocketurl !== 'http://') {
@ -88,6 +90,7 @@ class NewICDialog extends React.Component {
let websocketurl = true;
let type = true;
let category = true;
let manager = true;
if (this.state.name === '') {
name = false;
@ -97,6 +100,10 @@ class NewICDialog extends React.Component {
uuid = false;
}
if(this.state.managedexternally && manager === ''){
manager = false;
}
if (this.state.type === '') {
type = false;
}
@ -105,7 +112,7 @@ class NewICDialog extends React.Component {
category = false;
}
this.valid = name && uuid && websocketurl && type && category;
this.valid = name && uuid && websocketurl && type && category && manager;
// return state to control
if (target === 'name') return name ? "success" : "error";
@ -113,6 +120,7 @@ class NewICDialog extends React.Component {
if (target === 'websocketurl') return websocketurl ? "success" : "error";
if (target === 'type') return type ? "success" : "error";
if (target === 'category') return category ? "success" : "error";
if (target === 'manager') return manager ? "success" : "error";
return this.valid;
}
@ -131,8 +139,8 @@ class NewICDialog extends React.Component {
case "simulator":
typeOptions = ["dummy","generic","dpsim","rtlab","rscad","opalrt"];
break;
case "controller":
typeOptions = ["kubernetes","villas-controller"];
case "manager":
typeOptions = ["villas-node","villas-relay","generic"];
break;
case "gateway":
typeOptions = ["villas-node","villas-relay"];
@ -149,30 +157,49 @@ class NewICDialog extends React.Component {
return (
<Dialog show={this.props.show} title="New Infrastructure Component" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.validateForm()}>
<form>
<FormGroup controlId="managedexternally">
<OverlayTrigger key="3" placement={'left'} overlay={<Tooltip id={`tooltip-${"me"}`}>An externally managed component will show up in the list only after a VILLAScontroller for the component type has created the component and cannot be edited by users</Tooltip>} >
<FormCheck type={"checkbox"} label={"Managed externally"} defaultChecked={this.state.managedexternally} onChange={e => this.handleChange(e)}>
</FormCheck>
</OverlayTrigger>
</FormGroup>
{this.props.managers.length > 0 ?
<>
<FormGroup controlId="managedexternally">
<OverlayTrigger key="3" placement={'left'} overlay={<Tooltip id={`tooltip-${"me"}`}>An externally managed component is created and managed by an IC manager via AMQP</Tooltip>} >
<FormCheck type={"checkbox"} label={"Managed externally"} defaultChecked={this.state.managedexternally} onChange={e => this.handleChange(e)}>
</FormCheck>
</OverlayTrigger>
</FormGroup>
{this.state.managedexternally === true ?
<FormGroup controlId="manager" valid={this.validateForm('manager')}>
<OverlayTrigger key="0" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<FormLabel>Manager to create new IC *</FormLabel>
</OverlayTrigger>
<FormControl as="select" value={this.state.manager} onChange={(e) => this.handleChange(e)}>
{this.props.managers.map((m) => (
<option key={m.id} value={m.uuid}>{m.name}</option>
))}
</FormControl>
</FormGroup>
: <div/>
}
</>
: <div/>
}
<FormGroup controlId="name" valid={this.validateForm('name')}>
<OverlayTrigger key="0" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<OverlayTrigger key="1" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<FormLabel>Name *</FormLabel>
</OverlayTrigger>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="category" valid={this.validateForm('category')}>
<OverlayTrigger key="1" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<OverlayTrigger key="2" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<FormLabel>Category of component *</FormLabel>
</OverlayTrigger>
<FormControl as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
<option default>Select category</option>
<option>simulator</option>
<option>controller</option>
<option>service</option>
<option>gateway</option>
<option>equipment</option>
<option>manager</option>
</FormControl>
</FormGroup>
<FormGroup controlId="type" valid={this.validateForm('type')}>
@ -206,11 +233,15 @@ class NewICDialog extends React.Component {
<FormControl type="text" placeholder="Enter Description" value={this.state.description} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="uuid" valid={this.validateForm('uuid')}>
<FormLabel>UUID</FormLabel>
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
{this.state.managedexternally === false ?
<FormGroup controlId="uuid" valid={this.validateForm('uuid')}>
<FormLabel>UUID</FormLabel>
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid}
onChange={(e) => this.handleChange(e)}/>
<FormControl.Feedback/>
</FormGroup>
: <div/>
}
</form>
</Dialog>
);

View file

@ -16,7 +16,7 @@
******************************************************************************/
import React from 'react';
import {FormGroup, FormControl, FormLabel, Col, Button, ProgressBar} from 'react-bootstrap';
import { FormGroup, FormControl, FormLabel, Col, Row, Button, ProgressBar } from 'react-bootstrap';
import AppDispatcher from "../common/app-dispatcher";
import FileStore from "../file/file-store"
@ -38,8 +38,6 @@ class EditResultDialog extends React.Component {
uploadFile: null,
uploadProgress: 0,
files: null,
result: null,
resultExists: false,
};
}
@ -53,26 +51,38 @@ class EditResultDialog extends React.Component {
this.setState({ [event.target.id]: event.target.value });
};
isEmpty(val) {
return (val === undefined || val == null || val.length <= 0);
};
componentDidUpdate(prevProps, prevState) {
if (this.state.resultExists && this.props.files != prevProps.files) {
this.setState({files: FileStore.getState().filter(file => this.state.result.resultFileIDs.includes(file.id))});
}
if (this.props.result != prevProps.result && Object.keys(this.props.result).length != 0) {
this.setState({
id: this.props.result.id,
description: this.props.result.description,
result: this.props.result,
resultExists: true,
files: FileStore.getState().filter(file => this.props.result.resultFileIDs.includes(file.id)),
})
}
}
if (this.props.resultId != prevProps.resultId || this.props.results != prevProps.results) {
let result = this.props.results[this.props.resultId];
if (result && Object.keys(result).length != 0) {
let hasFiles = !this.isEmpty(result.resultFileIDs);
if (hasFiles) {
this.setState({
id: result.id,
description: result.description,
files: FileStore.getState().filter(file => result.resultFileIDs.includes(file.id)),
})
} else {
this.setState({
id: result.id,
description: result.description,
files: null,
})
}
}
}
};
selectUploadFile(event) {
this.setState({ uploadFile: event.target.files[0] });
};
startFileUpload(){
startFileUpload() {
const formData = new FormData();
formData.append("file", this.state.uploadFile);
@ -97,68 +107,98 @@ class EditResultDialog extends React.Component {
this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
};
deleteFile(index){
deleteFile(index) {
let file = this.state.files[index];
AppDispatcher.dispatch({
type: 'files/start-remove',
data: file,
type: 'resultfiles/start-remove',
resultID: this.state.id,
fileID: file.id,
token: this.props.sessionToken
});
}
submitDescription() {
let result = this.props.results[this.props.resultId];
if (!this.isEmpty(result)) {
result.description = this.state.description;
AppDispatcher.dispatch({
type: 'results/start-edit',
data: result,
token: this.props.sessionToken
});
}
}
render() {
return <Dialog show={this.props.show}
title={'Edit Result No. '+this.state.id}
buttonTitle='Close'
onClose={() => this.onClose()}
blendOutCancel = {true}
valid={true}
size = 'lg'>
title={'Edit Result No. ' + this.state.id}
buttonTitle='Close'
onClose={() => this.onClose()}
blendOutCancel={true}
valid={true}
size='lg'>
<div>
<FormGroup as={Col} controlId='description'>
<FormLabel column={false}>Description</FormLabel>
<FormControl type='text' placeholder='Enter description' value={this.state.description} onChange={this.handleChange} />
<FormControl.Feedback />
<Row style={{ float: 'center' }} >
<Col xs="auto">
<FormLabel>Description</FormLabel>
</Col>
<Col xs="auto">
<FormControl type='text' placeholder={this.state.description} value={this.state.description} onChange={this.handleChange} />
<FormControl.Feedback />
</Col>
<Col xs="auto">
<Button
type="submit"
onClick={() => this.submitDescription()}>
Save
</Button>
</Col>
</Row>
</FormGroup>
<Table data={this.state.files}>
<TableColumn title='ID' dataKey='id'/>
<TableColumn title='Name' dataKey='name'/>
<TableColumn title='Size (bytes)' dataKey='size'/>
<TableColumn title='Type' dataKey='type'/>
<TableColumn
title=''
deleteButton
onDelete={(index) => this.deleteFile(index)}
/>
<TableColumn title='ID' dataKey='id' />
<TableColumn title='Name' dataKey='name' />
<TableColumn title='Size (bytes)' dataKey='size' />
<TableColumn title='Type' dataKey='type' />
<TableColumn
title=''
deleteButton
onDelete={(index) => this.deleteFile(index)}
/>
</Table>
<FormGroup controlId='resultfile'>
<FormLabel>Add Result File</FormLabel>
<FormControl type='file' onChange={(event) => this.selectUploadFile(event)} />
<FormLabel>Add Result File</FormLabel>
<FormControl type='file' onChange={(event) => this.selectUploadFile(event)} />
</FormGroup>
<FormGroup as={Col} >
<Button
disabled={this.state.uploadFile === null}
onClick={() => this.startFileUpload()}>
Upload
<FormGroup as={Col} >
<Button
disabled={this.state.uploadFile === null}
onClick={() => this.startFileUpload()}>
Upload
</Button>
</FormGroup>
</FormGroup>
<FormGroup as={Col} >
<ProgressBar
striped={true}
animated={true}
now={this.state.uploadProgress}
label={this.state.uploadProgress + '%'}
/>
</FormGroup>
<FormGroup as={Col} >
<ProgressBar
striped={true}
animated={true}
now={this.state.uploadProgress}
label={this.state.uploadProgress + '%'}
/>
</FormGroup>
</div>
</Dialog>;
}

View file

@ -15,40 +15,14 @@
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import ArrayStore from '../common/array-store';
import ResultsDataManager from './results-data-manager';
import FilesDataManager from '../file/files-data-manager'
class ResultStore extends ArrayStore {
constructor() {
super('results', ResultsDataManager);
}
saveFile(state, action){
let fileID = parseInt(action.id)
state.forEach((element, index, array) => {
if (element.id === fileID) {
// save blob object
array[index]["data"] = new Blob([action.data.data], {type: action.data.type});
// update file type
array[index]["type"] = action.data.type;
if (array[index]["objectURL"] !== ''){
// free memory of previously generated object URL
URL.revokeObjectURL(array[index]["objectURL"]);
}
// create an object URL for the file
array[index]["objectURL"] = URL.createObjectURL(array[index]["data"])
}
});
// announce change to listeners
this.__emitChange();
return state
}
simplify(timestamp) {
let parts = timestamp.split("T");
let datestr = parts[0];
@ -67,17 +41,29 @@ class ResultStore extends ArrayStore {
reduce(state, action) {
switch (action.type) {
case 'results/loaded':
this.simplifyTimestamps(action.data);
if (Array.isArray(action.data)) {
this.simplifyTimestamps(action.data);
} else {
this.simplifyTimestamps([action.data]);
}
return super.reduce(state, action);
case 'results/added':
this.simplifyTimestamps([action.data]);
return super.reduce(state, action);
case 'results/edited':
this.simplifyTimestamps([action.data]);
return super.reduce(state, action);
case 'resultfiles/start-upload':
ResultsDataManager.uploadFile(action.data, action.resultID, action.token, action.progressCallback, action.finishedCallback, action.scenarioID);
return state;
case 'resultfiles/start-remove':
ResultsDataManager.removeFile(action.resultID, action.fileID, action.token);
return state;
default:
return super.reduce(state, action);
}

View file

@ -19,24 +19,24 @@ import RestDataManager from '../common/data-managers/rest-data-manager';
import RestAPI from '../common/api/rest-api';
import AppDispatcher from '../common/app-dispatcher';
class ResultsDataManager extends RestDataManager{
class ResultsDataManager extends RestDataManager {
constructor() {
super('result', '/results');
}
uploadFile(file, resultID, token = null, progressCallback = null, finishedCallback = null, scenarioID) {
RestAPI.upload(this.makeURL(this.url + '/' + resultID + '/file') , file, token, progressCallback, scenarioID).then(response => {
RestAPI.upload(this.makeURL(this.url + '/' + resultID + '/file'), file, token, progressCallback, scenarioID).then(response => {
AppDispatcher.dispatch({
type: 'files/uploaded',
});
// Trigger a results reload
// Trigger a result reload
AppDispatcher.dispatch({
type: 'results/start-load',
param: '?scenarioID=' + scenarioID,
token: token
data: resultID,
token: token,
});
// Trigger a files reload
@ -57,6 +57,23 @@ class ResultsDataManager extends RestDataManager{
});
}
removeFile(resultID, fileID, token) {
RestAPI.delete(this.makeURL(this.url + '/' + resultID + '/file/' + fileID), token).then(response => {
// reload result
AppDispatcher.dispatch({
type: 'results/start-load',
data: resultID,
token: token,
});
// update files
AppDispatcher.dispatch({
type: 'files/removed',
data: fileID,
token: token,
});
});
}
}
export default new ResultsDataManager();

View file

@ -103,6 +103,7 @@ class Scenario extends React.Component {
files: FileStore.getState().filter(file => file.scenarioID === parseInt(props.match.params.scenario, 10)),
ics: ICStore.getState(),
ExternalICInUse: false,
deleteConfigModal: false,
importConfigModal: false,
@ -113,9 +114,11 @@ class Scenario extends React.Component {
editResultsModal: prevState.editResultsModal || false,
modalResultsData: {},
modalResultsIndex: 0,
modalResultsIndex: prevState.modalResultsIndex,
newResultModal: false,
filesToDownload: [],
filesToDownload: prevState.filesToDownload,
zipfiles: prevState.zipfiles || false,
resultNodl: prevState.resultNodl,
editOutputSignalsModal: prevState.editOutputSignalsModal || false,
editInputSignalsModal: prevState.editInputSignalsModal || false,
@ -158,25 +161,28 @@ class Scenario extends React.Component {
componentDidUpdate(prevProps, prevState) {
// check whether file data has been loaded
if (this.state.filesToDownload.length > 0 ) {
if (this.state.filesToDownload.length === 1) {
let fileToDownload = FileStore.getState().filter(file => file.id === this.state.filesToDownload[0])
if (fileToDownload.length === 1 && fileToDownload[0].data) {
const blob = new Blob([fileToDownload[0].data], {type: fileToDownload[0].type});
FileSaver.saveAs(blob, fileToDownload[0].name);
this.setState({ filesToDownload: [] });
}
} else { // zip and save several files
let filesToDownload = FileStore.getState().filter(file => this.state.filesToDownload.includes(file.id) && file.data);
if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded
var zip = new JSZip();
filesToDownload.forEach(file => {
zip.file(file.name, file.data);
});
zip.generateAsync({type: "blob"}).then(function(content) {
saveAs(content, "results.zip");
});
this.setState({ filesToDownload: [] });
if (this.state.filesToDownload && this.state.filesToDownload.length > 0 ) {
if (this.state.files != prevState.files) {
if (!this.state.zipfiles) {
let fileToDownload = FileStore.getState().filter(file => file.id === this.state.filesToDownload[0])
if (fileToDownload.length === 1 && fileToDownload[0].data) {
const blob = new Blob([fileToDownload[0].data], {type: fileToDownload[0].type});
FileSaver.saveAs(blob, fileToDownload[0].name);
this.setState({ filesToDownload: [] });
}
} else { // zip and save one or more files (download all button)
let filesToDownload = FileStore.getState().filter(file => this.state.filesToDownload.includes(file.id) && file.data);
if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded
var zip = new JSZip();
filesToDownload.forEach(file => {
zip.file(file.name, file.data);
});
let zipname = "result_" + this.state.resultNodl + "_" + Date.now();
zip.generateAsync({type: "blob"}).then(function(content) {
saveAs(content, zipname);
});
this.setState({ filesToDownload: [] });
}
}
}
}
@ -361,9 +367,30 @@ class Scenario extends React.Component {
this.setState({ selectedConfigs: selectedConfigs });
}
runAction(action, delay) {
// delay in seconds
usesExternalIC(index){
let icID = this.state.configs[index].icID;
let ic = null;
for (let component of this.state.ics) {
if (component.id === this.state.configs[index].icID) {
ic = component;
}
}
if (ic == null) {
return false;
}
if (ic.managedexternally === true){
this.setState({ExternalICInUse: true})
return true
}
return false
}
runAction(action, when) {
if (action.data.action === 'none') {
console.warn("No command selected. Nothing was sent.");
return;
@ -386,8 +413,7 @@ class Scenario extends React.Component {
action.data.parameters = this.state.configs[index].startParameters;
}
// Unix time stamp + delay
action.data.when = Math.round(Date.now() / 1000.0 + delay)
action.data.when = when;
console.log("Sending action: ", action.data)
@ -613,21 +639,19 @@ class Scenario extends React.Component {
closeEditResultsModal() {
this.setState({ editResultsModal: false });
AppDispatcher.dispatch({
type: 'results/start-load',
token: this.state.sessionToken,
param: '?scenarioID=' + this.state.scenario.id
})
}
downloadResultData(param) {
let toDownload = [];
let zip = false;
if (typeof(param) === 'object') {
if (typeof(param) === 'object') { // download all files
toDownload = param.resultFileIDs;
} else {
zip = true;
this.setState({ filesToDownload: toDownload, zipfiles: zip, resultNodl: param.id });
} else { // download one file
toDownload.push(param);
this.setState({ filesToDownload: toDownload, zipfiles: zip});
}
toDownload.forEach(fileid => {
@ -637,8 +661,6 @@ class Scenario extends React.Component {
token: this.state.sessionToken
});
});
this.setState({ filesToDownload: toDownload });
}
closeDeleteResultsModal(confirmDelete) {
@ -736,7 +758,7 @@ class Scenario extends React.Component {
editButton
downloadAllButton
deleteButton
onEdit={index => this.setState({ editResultsModal: true, modalResultsData: this.state.results[index], modalResultsIndex: index })}
onEdit={index => this.setState({ editResultsModal: true, modalResultsIndex: index })}
onDownloadAll={(index) => this.downloadResultData(this.state.results[index])}
onDelete={(index) => this.setState({ deleteResultsModal: true, modalResultsData: this.state.results[index], modalResultsIndex: index })}
/>
@ -746,7 +768,8 @@ class Scenario extends React.Component {
sessionToken={this.state.sessionToken}
show={this.state.editResultsModal}
files={this.state.files}
result={this.state.modalResultsData}
results={this.state.results}
resultId={this.state.modalResultsIndex}
scenarioID={this.state.scenario.id}
onClose={this.closeEditResultsModal.bind(this)} />
<DeleteDialog title="result" name={this.state.modalResultsData.id} show={this.state.deleteResultsModal} onClose={(e) => this.closeDeleteResultsModal(e)} />
@ -772,12 +795,27 @@ class Scenario extends React.Component {
scenarioID={this.state.scenario.id}
/>
{/*Component Configurations table*/}
<h2 style={tableHeadingStyle}>Component Configurations</h2>
<h2 style={tableHeadingStyle}>Component Configurations
<OverlayTrigger
key={1}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"add"}`}> Add Component Configuration </Tooltip>} >
<Button onClick={() => this.addConfig()} style={buttonStyle}><Icon icon="plus" /></Button>
</OverlayTrigger>
<OverlayTrigger
key={2}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"import"}`}> Import Component Configuration </Tooltip>} >
<Button onClick={() => this.setState({ importConfigModal: true })} style={buttonStyle}><Icon icon="upload" /></Button>
</OverlayTrigger>
</h2>
<Table data={this.state.configs}>
<TableColumn checkbox onChecked={(index, event) => this.onConfigChecked(index, event)} width='30' />
<TableColumn
checkbox
checkboxDisabled={(index) => this.usesExternalIC(index)}
onChecked={(index, event) => this.onConfigChecked(index, event)}
width='30' />
<TableColumn title='Name' dataKey='name' />
<TableColumn title='Configuration file(s)' dataKey='fileIDs' modifier={(fileIDs) => this.getListOfFiles(fileIDs, ['json', 'JSON'])} />
<TableColumn
@ -819,24 +857,21 @@ class Scenario extends React.Component {
/>
</Table>
<div style={{ float: 'left' }}>
<ICAction
runDisabled={this.state.selectedConfigs.length === 0}
runAction={(action, delay) => this.runAction(action, delay)}
actions={[
{ id: '-1', title: 'Select command', data: { action: 'none' } },
{ id: '0', title: 'Start', data: { action: 'start' } },
{ id: '1', title: 'Stop', data: { action: 'stop' } },
{ id: '2', title: 'Pause', data: { action: 'pause' } },
{ id: '3', title: 'Resume', data: { action: 'resume' } }
]} />
</div>
<div style={{ float: 'right' }}>
<Button onClick={() => this.addConfig()} style={buttonStyle}><Icon icon="plus" /> Component Configuration</Button>
<Button onClick={() => this.setState({ importConfigModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
</div>
{ this.state.ExternalICInUse ? (
<div style={{float: 'left'}}>
<ICAction
runDisabled={this.state.selectedConfigs.length === 0}
runAction={(action, when) => this.runAction(action, when)}
actions={[
{id: '-1', title: 'Action', data: {action: 'none'}},
{id: '0', title: 'Start', data: {action: 'start'}},
{id: '1', title: 'Stop', data: {action: 'stop'}},
{id: '2', title: 'Pause', data: {action: 'pause'}},
{id: '3', title: 'Resume', data: {action: 'resume'}}
]}/>
</div>
) : (<div/>)
}
<div style={{ clear: 'both' }} />
<EditConfigDialog
@ -869,7 +904,20 @@ class Scenario extends React.Component {
/>
{/*Dashboard table*/}
<h2 style={tableHeadingStyle}>Dashboards</h2>
<h2 style={tableHeadingStyle}>Dashboards
<OverlayTrigger
key={1}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"add"}`}> Add Dashboard </Tooltip>} >
<Button onClick={() => this.setState({ newDashboardModal: true })} style={buttonStyle}><Icon icon="plus" /></Button>
</OverlayTrigger>
<OverlayTrigger
key={2}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"import"}`}> Import Dashboard </Tooltip>} >
<Button onClick={() => this.setState({ importDashboardModal: true })} style={buttonStyle}><Icon icon="upload" /></Button>
</OverlayTrigger>
</h2>
<Table data={this.state.dashboards}>
<TableColumn title='Name' dataKey='name' link='/dashboards/' linkKey='id' />
<TableColumn title='Grid' dataKey='grid' />
@ -887,13 +935,6 @@ class Scenario extends React.Component {
/>
</Table>
<div style={{ float: 'right' }}>
<Button onClick={() => this.setState({ newDashboardModal: true })} style={buttonStyle}><Icon icon="plus" /> Dashboard</Button>
<Button onClick={() => this.setState({ importDashboardModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
</div>
<div style={{ clear: 'both' }} />
<NewDashboardDialog show={this.state.newDashboardModal} onClose={data => this.closeNewDashboardModal(data)} />
<EditDashboardDialog show={this.state.dashboardEditModal} dashboard={this.state.modalDashboardData} onClose={data => this.closeEditDashboardModal(data)} />
<ImportDashboardDialog show={this.state.importDashboardModal} onClose={data => this.closeImportDashboardModal(data)} />
@ -901,18 +942,15 @@ class Scenario extends React.Component {
<DeleteDialog title="dashboard" name={this.state.modalDashboardData.name} show={this.state.deleteDashboardModal} onClose={(e) => this.closeDeleteDashboardModal(e)} />
{/*Result table*/}
<div>
<h2 style={tableHeadingStyle}>Results
<Button onClick={() => this.setState({ newResultModal: true })} style={buttonStyle}><Icon icon="plus" /></Button>
</h2>
</div>
<h2 style={tableHeadingStyle}>Results
<OverlayTrigger
key={1}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"add"}`}> Add Result </Tooltip>} >
<Button onClick={() => this.setState({ newResultModal: true })} style={buttonStyle}><Icon icon="plus" /></Button>
</OverlayTrigger>
</h2>
{resulttable}
{/*
<div style={{ float: 'right' }}>
<Button onClick={() => this.setState({ newResultModal: true })} style={buttonStyle}><Icon icon="plus" /> Result</Button>
</div>
*/}
<NewResultDialog show={this.state.newResultModal} onClose={data => this.closeNewResultModal(data)} />
{/*Scenario Users table*/}

View file

@ -17,7 +17,7 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button } from 'react-bootstrap';
import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap';
import FileSaver from 'file-saver';
import AppDispatcher from '../common/app-dispatcher';
@ -241,7 +241,20 @@ class Scenarios extends Component {
return (
<div className='section'>
<h1>Scenarios</h1>
<h1>Scenarios
<OverlayTrigger
key={1}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"add"}`}> Add Scenario </Tooltip>} >
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /></Button>
</OverlayTrigger>
<OverlayTrigger
key={2}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"import"}`}> Import Scenario </Tooltip>} >
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /></Button>
</OverlayTrigger>
</h1>
<Table data={this.state.scenarios}>
<TableColumn title='Name' dataKey='name' link='/scenarios/' linkKey='id' />
@ -260,13 +273,6 @@ class Scenarios extends Component {
/>
</Table>
<div style={{ float: 'right' }}>
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Scenario</Button>
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
</div>
<div style={{ clear: 'both' }} />
<NewScenarioDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditScenarioDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} scenario={this.state.modalScenario} />
<ImportScenarioDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />

View file

@ -0,0 +1,3 @@
.swagger-ui div.scheme-container {
display: none
}

View file

@ -398,6 +398,8 @@ div[class*="-widget"] label {
/* Begin time offset widget */
.time-offset {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-around;

View file

@ -70,14 +70,14 @@ class LoginForm extends Component {
<FormGroup controlId="username">
<FormLabel column={true}>Username</FormLabel>
<Col>
<FormControl type="text" placeholder="Username" onChange={(e) => this.handleChange(e)} />
<FormControl type="text" placeholder="Username" autoComplete="username" onChange={(e) => this.handleChange(e)} />
</Col>
</FormGroup>
<FormGroup controlId="password">
<FormLabel column={true}>Password</FormLabel>
<Col >
<FormControl type="password" placeholder="Password" onChange={(e) => this.handleChange(e)} />
<FormControl type="password" placeholder="Password" autoComplete="current-password" onChange={(e) => this.handleChange(e)} />
</Col>
</FormGroup>
@ -95,11 +95,11 @@ class LoginForm extends Component {
<Button variant="link" size="sm" style={{marginLeft: 85}} onClick={() => this.openRecoverPassword()}>Forgot your password?</Button>
</Col>
</FormGroup>
<RecoverPassword show={this.state.forgottenPassword} onClose={() => this.closeRecoverPassword()} sessionToken={this.props.sessionToken} />
</Form>
);
}
}

View file

@ -17,7 +17,7 @@
import React from 'react';
import Dialog from '../common/dialogs/dialog';
import Config from '../config.js';
import Config from '../config';
class RecoverPassword extends React.Component {
@ -29,13 +29,10 @@ class RecoverPassword extends React.Component {
}
}
onClose() {
this.props.onClose();
}
render() {
return (
<Dialog show={this.props.show} title="Recover password" buttonTitle="Close" onClose={(c) => this.onClose(c)} blendOutCancel = {true} valid={true} size = 'lg'>
@ -43,7 +40,7 @@ class RecoverPassword extends React.Component {
<div>Please contact:</div>
<div>{this.state.admin.name}</div>
<div>E-Mail:</div>
<a href={`mailto:${this.state.admin.mail}`}>{this.state.admin.mail}</a>
<a href={`mailto:${this.state.admin.mail}`}>{this.state.admin.mail}</a>
</div>
</Dialog>
);

View file

@ -17,7 +17,7 @@
import React, { Component } from 'react';
import { Container } from 'flux/utils';
import { Button } from 'react-bootstrap';
import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap';
import AppDispatcher from '../common/app-dispatcher';
import UsersStore from './users-store';
@ -130,9 +130,22 @@ class Users extends Component {
render() {
const buttonStyle = {
marginLeft: '10px'
};
return (
<div>
<h1>Users</h1>
<h1>Users
<OverlayTrigger
key={1}
placement={'top'}
overlay={<Tooltip id={`tooltip-${"add"}`}> Add User </Tooltip>} >
<Button style={buttonStyle} onClick={() => this.setState({ newModal: true })}><Icon icon='plus' /> </Button>
</OverlayTrigger>
</h1>
<Table data={this.state.users}>
<TableColumn title='Username' width='150' dataKey='username' />
@ -143,7 +156,7 @@ class Users extends Component {
<TableColumn width='200' editButton deleteButton onEdit={index => this.setState({ editModal: true, modalData: this.state.users[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.users[index] })} />
</Table>
<Button onClick={() => this.setState({ newModal: true })}><Icon icon='plus' /> User</Button>
<NewUserDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
<EditUserDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} user={this.state.modalData} />

View file

@ -170,7 +170,8 @@ export default function CreateControls(widgetType = null, widget = null, session
<EditWidgetNumberControl key={1} widget={widget} controlId={'customProperties.threshold_yellow'} label={'Threshold yellow'} defaultValue={0} handleChange={(e) => handleChange(e)} />,
<EditWidgetNumberControl key={2} widget={widget} controlId={'customProperties.threshold_red'} label={'Threshold red'} defaultValue={0} handleChange={(e) => handleChange(e)} />,
<EditWidgetCheckboxControl key={3} widget={widget} controlId={'customProperties.horizontal'} input text="Horizontal" handleChange={e => handleChange(e)} />,
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'customProperties.showOffset'} input text="showOffset" handleChange={e => handleChange(e)} />,
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'customProperties.showName'} input text="showName" handleChange={e => handleChange(e)} />,
<EditWidgetCheckboxControl key={5} widget={widget} controlId={'customProperties.showOffset'} input text="showOffset" handleChange={e => handleChange(e)} />,
);
break;

View file

@ -19,6 +19,8 @@ import React, { Component } from 'react';
import { FormGroup, OverlayTrigger, Tooltip , FormLabel, Button } from 'react-bootstrap';
import ColorPicker from './color-picker'
import Icon from "../../common/icon";
import {scaleOrdinal} from "d3-scale";
import {schemeCategory10} from "d3-scale-chromatic";
// schemeCategory20 no longer available in d3
@ -36,8 +38,20 @@ class EditWidgetPlotColorsControl extends Component {
}
static getDerivedStateFromProps(props, state){
let widget = props.widget;
if(widget.customProperties.lineColors === undefined || widget.customProperties.lineColors === null){
// for backwards compatibility with old plots
widget.customProperties.lineColors = []
const newLineColor = scaleOrdinal(schemeCategory10);
for (let signalID of widget.signalIDs){
widget.customProperties.lineColors.push(newLineColor(signalID))
}
}
return {
widget: props.widget
widget: widget
};
}
@ -62,9 +76,9 @@ class EditWidgetPlotColorsControl extends Component {
this.setState({selectedIndex: null});
}
}
render() {
return (
<FormGroup>
<FormLabel>Line Colors</FormLabel>
@ -81,7 +95,7 @@ class EditWidgetPlotColorsControl extends Component {
}
let signal = this.props.signals.find(signal => signal.id === signalID);
return (<OverlayTrigger key={idx} placement={'bottom'} overlay={<Tooltip id={'tooltip-${"signal name"}'}>{signal.name}</Tooltip>}>
<Button
style={style} key={idx} onClick={i => this.editLineColor(signalID)} ><Icon icon="pen" /></Button>

View file

@ -168,6 +168,7 @@ class WidgetFactory {
widget.customProperties.valueMin = 0;
widget.customProperties.valueMax = 1;
widget.customProperties.valueUseMinMax = false;
widget.customProperties.lockAspect = true;
break;
case 'Box':
widget.minWidth = 50;
@ -209,6 +210,7 @@ class WidgetFactory {
widget.customProperties.horizontal = true;
widget.customProperties.showOffset = true;
widget.customProperties.lockAspect = true;
widget.customProperties.showName = true;
break;
default:

View file

@ -20,7 +20,7 @@ import { scaleOrdinal} from 'd3-scale';
import {schemeCategory10} from 'd3-scale-chromatic'
function Legend(props){
const signal = props.sig;
const hasScalingFactor = (signal.scalingFactor !== 1);
@ -52,10 +52,15 @@ class PlotLegend extends React.Component {
return <div className="plot-legend">
<ul>
{
{ this.props.lineColors !== undefined && this.props.lineColors != null ? (
this.props.signals.map( signal =>
<Legend key={signal.id} sig={signal} lineColor={this.props.lineColors[signal.id]}/>
)}
)) : (
this.props.signals.map( signal =>
<Legend key={signal.id} sig={signal} lineColor={"undefined"}/>
))
}
</ul>
</div>;
}

View file

@ -207,6 +207,11 @@ class Plot extends React.Component {
const lines = this.state.data.map((values, index) => {
let signalID = this.props.signalIDs[index];
if(this.props.lineColors === undefined || this.props.lineColors === null){
this.props.lineColors = [] // for backwards compatibility
}
if (typeof this.props.lineColors[signalID] === "undefined") {
this.props.lineColors[signalID] = newLineColor(signalID);
}

View file

@ -71,7 +71,10 @@ class WidgetTimeOffset extends Component {
<div className="time-offset">
{this.props.widget.customProperties.icID !== -1 ?
(<span></span>) : (<span>no IC</span>)
}
}
{this.props.widget.customProperties.icID !== -1 && this.props.widget.customProperties.showName ?
(<span>{this.state.icName}</span>) : (<span></span>)
}
<OverlayTrigger key={0} placement={'left'} overlay={<Tooltip id={`tooltip-${"traffic-light"}`}>
{this.props.widget.customProperties.icID !== -1 ?
(<span>{this.state.icName}<br></br>Offset: {this.state.timeOffset + "s"}</span>)