From ea82a19bab3415031c274c4103372fd2100b4f00 Mon Sep 17 00:00:00 2001 From: irismarie Date: Tue, 16 Mar 2021 16:48:17 +0100 Subject: [PATCH 1/6] adapt to new color scheme --- src/result/edit-result.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/result/edit-result.js b/src/result/edit-result.js index 2b8b13f..aba2117 100644 --- a/src/result/edit-result.js +++ b/src/result/edit-result.js @@ -154,11 +154,13 @@ class EditResultDialog extends React.Component { + + @@ -194,11 +196,14 @@ class EditResultDialog extends React.Component { this.selectUploadFile(event)} /> + + From 31e1da9ee180481ca5b5f2c7818761d6b25a30a0 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 17 Mar 2021 09:06:08 +0100 Subject: [PATCH 2/6] add https:// scheme to result and model files in start action, closes #301 --- src/ic/ic-action.js | 6 +++--- src/ic/ic-store.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ic/ic-action.js b/src/ic/ic-action.js index df4c17f..a33f73f 100644 --- a/src/ic/ic-action.js +++ b/src/ic/ic-action.js @@ -74,14 +74,14 @@ class ICAction extends React.Component { * see: https://villas.fein-aachen.org/doc/controller-protocol.html */ - if (newAction.action == "create" || newAction.action === "delete") { + if (newAction.action === "create" || newAction.action === "delete") { // prepare parameters for delete incl. correct IC id newAction["parameters"] = {}; - if (newAction.action == "delete") { + if (newAction.action === "delete") { newAction.parameters["uuid"] = ic.uuid; } - else if (newAction.action == "create") { + else if (newAction.action === "create") { newAction.parameters = ic.statusupdateraw.properties; } diff --git a/src/ic/ic-store.js b/src/ic/ic-store.js index d892c7d..bc9aa18 100644 --- a/src/ic/ic-store.js +++ b/src/ic/ic-store.js @@ -88,14 +88,14 @@ class InfrastructureComponentStore extends ArrayStore { // adapt URL for newly created result ID a.results.url = a.results.url.replace("RESULTID", action.data.result.id); a.results.url = ICsDataManager.makeURL(a.results.url); - a.results.url = window.location.host + a.results.url; + a.results.url = 'https://' + window.location.host + a.results.url; } if (a.model !== undefined && a.model != null && JSON.stringify(a.model) !== JSON.stringify({})) { // adapt URL(s) for model file let modelURLs = [] for (let url of a.model.url){ let modifiedURL = ICsDataManager.makeURL(url); - modifiedURL = window.location.host + modifiedURL; + modifiedURL = 'https://' + window.location.host + modifiedURL; modelURLs.push(modifiedURL) } a.model.url = modelURLs From 0cf3294816a8339b8ba687970e026317fb30221f Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 18 Mar 2021 14:41:02 +0100 Subject: [PATCH 3/6] make upload button in edit files dialog fully clickable; close #289 --- src/dashboard/dashboard.js | 4 ++-- src/file/edit-files.js | 27 ++++++++++++++------------- src/scenario/scenario.js | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index d627c77..fe3ca4f 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -21,7 +21,7 @@ import Fullscreenable from 'react-fullscreenable'; import classNames from 'classnames'; import EditWidget from '../widget/edit-widget/edit-widget'; -import EditFiles from '../file/edit-files'; +import EditFilesDialog from '../file/edit-files'; import EditSignalMappingDialog from "../signal/edit-signal-mapping"; import WidgetContextMenu from '../widget/widget-context-menu'; import WidgetToolbox from '../widget/widget-toolbox'; @@ -560,7 +560,7 @@ class Dashboard extends Component { ics={this.state.ics} /> - - - this.selectUploadFile(event)} - /> - - - +
+
Add file
+ + + this.selectUploadFile(event)} /> + + + - + + +
+ +
diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 73767c9..4557dea 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -36,7 +36,7 @@ import ImportConfigDialog from '../componentconfig/import-config'; import ImportDashboardDialog from "../dashboard/import-dashboard"; import NewDashboardDialog from "../dashboard/new-dashboard"; import EditDashboardDialog from '../dashboard/edit-dashboard'; -import EditFiles from '../file/edit-files' +import EditFilesDialog from '../file/edit-files' import NewResultDialog from '../result/new-result'; import EditResultDialog from '../result/edit-result'; import ResultConfigDialog from '../result/result-configs-dialog'; @@ -746,7 +746,7 @@ class Scenario extends React.Component {

{this.state.scenario.name}

- Date: Thu, 18 Mar 2021 16:28:27 +0100 Subject: [PATCH 4/6] check if jwt is expired and redirect to logout if so #298 --- package-lock.json | 244 +++++++++++++++++++++++++++++++------ package.json | 3 +- src/app.js | 34 ++++-- src/user/login-complete.js | 8 +- 4 files changed, 239 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index fbb7f6e..afa6707 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3903,6 +3903,11 @@ "isarray": "^1.0.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -5648,6 +5653,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5664,23 +5677,23 @@ "integrity": "sha512-eFDC+yVQpEhtlapk4CYDPfV9ajF9cEof5TBcO49L1ETO+aYogrKWDmYpZyxBScMNe8Bo/gJamH4amQ4yyvXg4g==" }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, @@ -6944,6 +6957,11 @@ } } }, + "fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + }, "fibers": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz", @@ -8180,11 +8198,6 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, - "immer": { - "version": "7.0.9", - "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", @@ -10723,6 +10736,30 @@ } } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -10754,6 +10791,25 @@ "set-immediate-shim": "~1.0.1" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -11089,11 +11145,46 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -14414,9 +14505,9 @@ } }, "react-dev-utils": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.1.tgz", - "integrity": "sha512-rlgpCupaW6qQqvu0hvv2FDv40QG427fjghV56XyPcP5aKtOAPzNAhQ7bHqk1YdS2vpW1W7aSV3JobedxuPlBAA==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", + "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", "requires": { "@babel/code-frame": "7.10.4", "address": "1.1.2", @@ -14431,13 +14522,13 @@ "global-modules": "2.0.0", "globby": "11.0.1", "gzip-size": "5.1.1", - "immer": "7.0.9", + "immer": "8.0.1", "is-root": "2.1.0", "loader-utils": "2.0.0", "open": "^7.0.2", "pkg-up": "3.1.0", "prompts": "2.4.0", - "react-error-overlay": "^6.0.8", + "react-error-overlay": "^6.0.9", "recursive-readdir": "2.2.2", "shell-quote": "1.7.2", "strip-ansi": "6.0.0", @@ -14470,11 +14561,21 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" }, + "immer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", + "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==" + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "react-error-overlay": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14574,11 +14675,6 @@ "prop-types": "^15.6.0" } }, - "react-error-overlay": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", - "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" - }, "react-fullscreenable": { "version": "2.5.1-0", "resolved": "https://registry.npmjs.org/react-fullscreenable/-/react-fullscreenable-2.5.1-0.tgz", @@ -14626,14 +14722,47 @@ "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" }, "react-json-view": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.19.1.tgz", - "integrity": "sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==", + "version": "1.21.3", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", + "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", "requires": { - "flux": "^3.1.3", + "flux": "^4.0.1", "react-base16-styling": "^0.6.0", "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^6.1.0" + "react-textarea-autosize": "^8.3.2" + }, + "dependencies": { + "fbemitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", + "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", + "requires": { + "fbjs": "^3.0.0" + } + }, + "fbjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz", + "integrity": "sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg==", + "requires": { + "cross-fetch": "^3.0.4", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + } + }, + "flux": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.1.tgz", + "integrity": "sha512-emk4RCvJ8RzNP2lNpphKnG7r18q8elDYNAPx7xn+bDeOIo9FFfxEfIQ2y6YbQNmnsGD3nH1noxtLE64Puz1bRQ==", + "requires": { + "fbemitter": "^3.0.0", + "fbjs": "^3.0.0" + } + } } }, "react-lifecycles-compat": { @@ -14839,11 +14968,28 @@ } }, "react-textarea-autosize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", - "integrity": "sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.2.tgz", + "integrity": "sha512-JrMWVgQSaExQByP3ggI1eA8zF4mF0+ddVuX7acUeK2V7bmrpjVOY72vmLz2IXFJSAXoY3D80nEzrn0GWajWK3Q==", "requires": { - "prop-types": "^15.6.0" + "@babel/runtime": "^7.10.2", + "use-composed-ref": "^1.0.0", + "use-latest": "^1.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } } }, "react-trafficlight": { @@ -17383,6 +17529,11 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "ts-essentials": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz", + "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==" + }, "ts-node": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", @@ -17751,6 +17902,27 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-composed-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.1.0.tgz", + "integrity": "sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg==", + "requires": { + "ts-essentials": "^2.0.3" + } + }, + "use-isomorphic-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", + "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" + }, + "use-latest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz", + "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==", + "requires": { + "use-isomorphic-layout-effect": "^1.0.0" + } + }, "utf-8-validate": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.3.tgz", diff --git a/package.json b/package.json index a19b1cc..413eda9 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "gaugeJS": "^1.3.7", "handlebars": "^4.7.6", "jquery": "^3.5.1", + "jsonwebtoken": "^8.5.1", "jszip": "^3.5.0", "libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git", "lodash": "^4.17.20", @@ -48,7 +49,7 @@ "react-dom": "^16.14.0", "react-fullscreenable": "^2.5.1-0", "react-grid-system": "^7.1.1", - "react-json-view": "^1.19.1", + "react-json-view": "^1.21.3", "react-notification-system": "^0.4.0", "react-rnd": "^10.2.3", "react-router": "^5.2.0", diff --git a/src/app.js b/src/app.js index 8b2655a..934c752 100644 --- a/src/app.js +++ b/src/app.js @@ -20,7 +20,7 @@ import { DndProvider } from 'react-dnd'; import HTML5Backend from 'react-dnd-html5-backend'; import NotificationSystem from 'react-notification-system'; import { Redirect, Route } from 'react-router-dom'; -import { Hidden } from 'react-grid-system' +import jwt from 'jsonwebtoken' import AppDispatcher from './common/app-dispatcher'; import NotificationsDataManager from './common/data-managers/notifications-data-manager'; @@ -38,6 +38,7 @@ import Users from './user/users'; import User from './user/user'; import APIBrowser from './common/api-browser'; + import './styles/app.css'; class App extends React.Component { @@ -58,22 +59,37 @@ class App extends React.Component { // if token stored locally, we are already logged-in let token = localStorage.getItem("token"); if (token != null && token !== '') { - let currentUser = JSON.parse(localStorage.getItem("currentUser")); - console.log("Already logged-in") - AppDispatcher.dispatch({ - type: 'users/logged-in', - token: token, - currentUser: currentUser - }); + + let isExpired = this.tokenIsExpired(token); + if (isExpired) { + console.log("Token expired") + AppDispatcher.dispatch({ + type: 'users/logout' + }); + } else { + let currentUser = JSON.parse(localStorage.getItem("currentUser")); + console.log("Already logged-in") + AppDispatcher.dispatch({ + type: 'users/logged-in', + token: token, + currentUser: currentUser + }); + } } } + tokenIsExpired(token){ + let decodedToken = jwt.decode(token); + let timeNow = (new Date().getTime() + 1) / 1000; + return decodedToken.exp < timeNow; + } + render() { let token = localStorage.getItem("token"); let currentUserRaw = localStorage.getItem("currentUser"); - if (token == null || token === "" || currentUserRaw == null || currentUserRaw === "") { + if ((token == null || token === "" || currentUserRaw == null || currentUserRaw === "") || this.tokenIsExpired(token)) { console.log("APP redirecting to logout/ login") return (); } diff --git a/src/user/login-complete.js b/src/user/login-complete.js index 9ed2fc7..7df0d00 100644 --- a/src/user/login-complete.js +++ b/src/user/login-complete.js @@ -69,7 +69,7 @@ class LoginComplete extends React.Component { } startTimer() { - if (this.timer == 0 && this.state.secondsToWait > 0) { + if (this.timer === 0 && this.state.secondsToWait > 0) { // call function 'countDown' every 1000ms this.timer = setInterval(this.countDown, 1000); } @@ -80,7 +80,7 @@ class LoginComplete extends React.Component { this.setState({secondsToWait: seconds}); // waiting time over, stop counting down - if (seconds == 0) { + if (seconds === 0) { clearInterval(this.timer); } } @@ -90,11 +90,11 @@ class LoginComplete extends React.Component { this.stopTimer(); return (); } - else if (this.state.secondsToWait == 0) { + else if (this.state.secondsToWait === 0) { this.stopTimer(); return (); } else { - return
+ return
Date: Fri, 19 Mar 2021 14:08:40 +0100 Subject: [PATCH 5/6] assign a fixed width to each table column in scenario, test if it makes a difference for #307 --- src/scenario/scenario.js | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 4557dea..80f2e60 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -168,7 +168,7 @@ class Scenario extends React.Component { componentDidUpdate(prevProps, prevState) { // check whether file data has been loaded if (this.state.filesToDownload && this.state.filesToDownload.length > 0) { - if (this.state.files != prevState.files) { + 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) { @@ -777,44 +777,50 @@ class Scenario extends React.Component { checkbox checkboxDisabled={(index) => !this.usesExternalIC(index)} onChecked={(index, event) => this.onConfigChecked(index, event)} - width='30' + width={20} /> {this.state.currentUser.role === "Admin" ? : <> } this.setState({ editOutputSignalsModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} + width={150} /> this.setState({ editInputSignalsModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} + width={150} /> this.signalsAutoConf(index)} + width={150} /> this.getICName(icID)} + width={300} /> : <> } @@ -913,13 +920,17 @@ class Scenario extends React.Component { dataKey='name' link='/dashboards/' linkKey='id' + width={300} /> + dataKey='grid' + width={100} + /> + this.modifyResultNoColumn(id, result)} + width={70} /> this.downloadResultData(index)} + width={300} /> : <> } this.setState({ From eaefeeb3100921e85c2cd0108c38cb7c045a2fd0 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 19 Mar 2021 16:40:54 +0100 Subject: [PATCH 6/6] modularize scenario.js file #270 --- src/componentconfig/config-table.js | 434 +++++++++++ src/dashboard/dashboard-table.js | 229 ++++++ src/result/result-table.js | 244 ++++++ src/scenario/scenario-users-table.js | 169 ++++ src/scenario/scenario.js | 1061 ++------------------------ 5 files changed, 1142 insertions(+), 995 deletions(-) create mode 100644 src/componentconfig/config-table.js create mode 100644 src/dashboard/dashboard-table.js create mode 100644 src/result/result-table.js create mode 100644 src/scenario/scenario-users-table.js diff --git a/src/componentconfig/config-table.js b/src/componentconfig/config-table.js new file mode 100644 index 0000000..1452949 --- /dev/null +++ b/src/componentconfig/config-table.js @@ -0,0 +1,434 @@ +/** + * 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 . + ******************************************************************************/ + +import React, {Component} from "react"; +import FileSaver from 'file-saver'; +import IconButton from "../common/icon-button"; +import Table from "../common/table"; +import TableColumn from "../common/table-column"; +import DeleteDialog from "../common/dialogs/delete-dialog"; +import AppDispatcher from "../common/app-dispatcher"; +import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; +import ICAction from "../ic/ic-action"; +import EditConfigDialog from "./edit-config"; +import ImportConfigDialog from "./import-config"; +import EditSignalMappingDialog from "../signal/edit-signal-mapping"; + +class ConfigTable extends Component { + + constructor() { + super(); + + this.state = { + editConfigModal: false, + modalConfigData: {}, + modalConfigIndex: 0, + deleteConfigModal: false, + importConfigModal: false, + newConfig: false, + selectedConfigs: [], + ExternalICInUse: false, + editOutputSignalsModal: false, + editInputSignalsModal: false, + } + } + + addConfig() { + const config = { + scenarioID: this.props.scenario.id, + name: 'New Component Configuration', + icID: this.props.ics.length > 0 ? this.props.ics[0].id : null, + startParameters: {}, + }; + + AppDispatcher.dispatch({ + type: 'configs/start-add', + data: config, + token: this.props.sessionToken + }); + + this.setState({ newConfig: true }); + + } + + closeEditConfigModal(data) { + this.setState({ editConfigModal: false, newConfig: false }); + + if (data) { + AppDispatcher.dispatch({ + type: 'configs/start-edit', + data: data, + token: this.props.sessionToken, + }); + } + } + + closeDeleteConfigModal(confirmDelete) { + this.setState({ deleteConfigModal: false }); + + if (confirmDelete === false) { + return; + } + + AppDispatcher.dispatch({ + type: 'configs/start-remove', + data: this.state.modalConfigData, + token: this.props.sessionToken + }); + } + + importConfig(data) { + this.setState({ importConfigModal: false }); + + if (data == null) { + return; + } + + let newConfig = JSON.parse(JSON.stringify(data.config)) + + newConfig["scenarioID"] = this.props.scenario.id; + newConfig.name = data.name; + + AppDispatcher.dispatch({ + type: 'configs/start-add', + data: newConfig, + token: this.props.sessionToken + }); + } + + copyConfig(index) { + let config = JSON.parse(JSON.stringify(this.props.configs[index])); + + let signals = JSON.parse(JSON.stringify(this.props.signals.filter(s => s.configID === parseInt(config.id, 10)))); + signals.forEach((signal) => { + delete signal.configID; + delete signal.id; + }) + + // two separate lists for inputMapping and outputMapping + let inputSignals = signals.filter(s => s.direction === 'in'); + let outputSignals = signals.filter(s => s.direction === 'out'); + + // add signal mappings to config + config["inputMapping"] = inputSignals; + config["outputMapping"] = outputSignals; + + delete config.id; + delete config.scenarioID; + delete config.inputLength; + delete config.outputLength; + + return config; + } + + exportConfig(index) { + let config = this.copyConfig(index); + + // show save dialog + const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); + FileSaver.saveAs(blob, 'config-' + config.name + '.json'); + } + + duplicateConfig(index) { + let newConfig = this.copyConfig(index); + newConfig["scenarioID"] = this.props.scenario.id; + newConfig.name = newConfig.name + '_copy'; + + AppDispatcher.dispatch({ + type: 'configs/start-add', + data: newConfig, + token: this.props.sessionToken + }); + } + + onConfigChecked(index, event) { + const selectedConfigs = Object.assign([], this.state.selectedConfigs); + for (let key in selectedConfigs) { + if (selectedConfigs[key] === index) { + // update existing entry + if (event.target.checked) { + return; + } + + selectedConfigs.splice(key, 1); + + this.setState({ selectedConfigs: selectedConfigs }); + return; + } + } + + // add new entry + if (event.target.checked === false) { + return; + } + + selectedConfigs.push(index); + this.setState({ selectedConfigs: selectedConfigs }); + } + + usesExternalIC(index) { + let icID = this.props.configs[index].icID; + + let ic = null; + for (let component of this.props.ics) { + if (component.id === icID) { + ic = component; + } + } + + if (ic == null) { + return false; + } + + if (ic.managedexternally === true) { + this.setState({ ExternalICInUse: true }) + return true + } + + return false + } + + getICName(icID) { + for (let ic of this.props.ics) { + if (ic.id === icID) { + return ic.name || ic.uuid; + } + } + } + + /* ############################################## + * Signal modification methods + ############################################## */ + + closeEditSignalsModal(direction) { + + // reload the config + AppDispatcher.dispatch({ + type: 'configs/start-load', + data: this.state.modalConfigData.id, + token: this.props.sessionToken + }); + + if (direction === "in") { + this.setState({ editInputSignalsModal: false }); + } else if (direction === "out") { + this.setState({ editOutputSignalsModal: false }); + } + } + + signalsAutoConf(index) { + let componentConfig = this.props.configs[index]; + // determine apiurl of infrastructure component + let ic = this.props.ics.find(ic => ic.id === componentConfig.icID) + if (!ic.type.includes("villas-node")) { + let message = "Cannot autoconfigure signals for IC type " + ic.type + " of category " + ic.category + ". This is only possible for gateway ICs of type 'VILLASnode'." + console.warn(message); + + const SIGNAL_AUTOCONF_WARN_NOTIFICATION = { + title: 'Failed to load signal config for IC ' + ic.name, + message: message, + level: 'warning' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_WARN_NOTIFICATION); + return; + } + + let splitWebsocketURL = ic.websocketurl.split("/") + + AppDispatcher.dispatch({ + type: 'signals/start-autoconfig', + url: ic.apiurl + "/nodes", + socketname: splitWebsocketURL[splitWebsocketURL.length - 1], + token: this.props.sessionToken, + configID: componentConfig.id + }); + + } + + startPintura(configIndex) { + let config = this.props.configs[configIndex]; + + // get xml / CIM file + let files = [] + for (let id of config.fileIDs) { + for (let file of this.props.files) { + if (file.id === id && ['xml'].some(e => file.type.includes(e))) { + files.push(file); + } + } + } + + if (files.length > 1) { + // more than one CIM file... + console.warn("There is more than one CIM file selected in this component configuration. I will open them all in a separate tab.") + } + + let baseURL = 'aaa.bbb.ccc.ddd/api/v2/files/' + for (let file of files) { + // endpoint param serves for download and upload of CIM file, token is required for authentication + let params = { + token: this.props.sessionToken, + endpoint: baseURL + String(file.id), + } + + // TODO start Pintura for editing CIM/ XML file from here + console.warn("Starting Pintura... and nothing happens so far :-) ", params) + } + } + + render() { + return ( +
+ {/*Component Configurations table*/} +

Component Configurations + + this.addConfig()} + icon='plus' + /> + this.setState({ importConfigModal: true })} + icon='upload' + /> + +

+ + !this.usesExternalIC(index)} + onChecked={(index, event) => this.onConfigChecked(index, event)} + width={20} + /> + {this.props.currentUser.role === "Admin" ? + + : <> + } + + this.setState({ editOutputSignalsModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })} + width={150} + /> + this.setState({ editInputSignalsModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })} + width={150} + /> + this.signalsAutoConf(index)} + width={150} + /> + this.getICName(icID)} + width={200} + /> + this.setState({ editConfigModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })} + onDelete={(index) => this.setState({ deleteConfigModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })} + onExport={index => this.exportConfig(index)} + onDuplicate={index => this.duplicateConfig(index)} + /> +
+ + {this.state.ExternalICInUse ? +
+ this.copyConfig(index)} + token = {this.props.sessionToken} + actions={[ + { 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' } } + ]} /> +
+ :
+ } + +
+ + this.closeEditConfigModal(data)} + config={this.state.modalConfigData} + ics={this.props.ics} + files={this.props.files} + sessionToken={this.props.sessionToken} + /> + this.importConfig(data)} + ics={this.props.ics} + /> + this.closeDeleteConfigModal(c)} + /> + this.closeEditSignalsModal(direction)} + direction="Output" + signals={this.props.signals} + configID={this.state.modalConfigData.id} + sessionToken={this.props.sessionToken} + /> + this.closeEditSignalsModal(direction)} + direction="Input" + signals={this.props.signals} + configID={this.state.modalConfigData.id} + sessionToken={this.props.sessionToken} + /> +
+ ) + } +} + +export default ConfigTable; diff --git a/src/dashboard/dashboard-table.js b/src/dashboard/dashboard-table.js new file mode 100644 index 0000000..89f5bf0 --- /dev/null +++ b/src/dashboard/dashboard-table.js @@ -0,0 +1,229 @@ +/** + * 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 . + ******************************************************************************/ + +import React, {Component} from "react"; +import FileSaver from 'file-saver'; +import IconButton from "../common/icon-button"; +import Table from "../common/table"; +import TableColumn from "../common/table-column"; +import NewDashboardDialog from "./new-dashboard"; +import EditDashboardDialog from "./edit-dashboard"; +import ImportDashboardDialog from "./import-dashboard"; +import DeleteDialog from "../common/dialogs/delete-dialog"; +import AppDispatcher from "../common/app-dispatcher"; + +class DashboardTable extends Component { + + constructor() { + super(); + + this.state = { + newDashboardModal: false, + deleteDashboardModal: false, + importDashboardModal: false, + modalDashboardData: {}, + dashboardEditModal: false, + } + } + + closeNewDashboardModal(data) { + this.setState({ newDashboardModal: false }); + if (data) { + // TODO: 'newDashboard' not used, check this + let newDashboard = data; + // add default grid value and scenarioID + newDashboard["grid"] = 15; + newDashboard["scenarioID"] = this.props.scenario.id; + + AppDispatcher.dispatch({ + type: 'dashboards/start-add', + data, + token: this.props.sessionToken, + }); + } + } + + closeEditDashboardModal(data) { + this.setState({ dashboardEditModal: false }); + + let editDashboard = this.state.modalDashboardData; + + if (data != null) { + editDashboard.name = data.name; + AppDispatcher.dispatch({ + type: 'dashboards/start-edit', + data: editDashboard, + token: this.props.sessionToken + }); + } + } + + closeDeleteDashboardModal(confirmDelete) { + this.setState({ deleteDashboardModal: false }); + + if (confirmDelete === false) { + return; + } + + AppDispatcher.dispatch({ + type: 'dashboards/start-remove', + data: this.state.modalDashboardData, + token: this.props.sessionToken, + }); + } + + closeImportDashboardModal(data) { + this.setState({ importDashboardModal: false }); + + if (data) { + let newDashboard = JSON.parse(JSON.stringify(data)); + newDashboard["scenarioID"] = this.props.scenario.id; + + AppDispatcher.dispatch({ + type: 'dashboards/start-add', + data: newDashboard, + token: this.props.sessionToken, + }); + } + } + + copyDashboard(index) { + let dashboard = JSON.parse(JSON.stringify(this.props.dashboards[index])); + + let widgets = JSON.parse(JSON.stringify(this.props.widgets.filter(w => w.dashboardID === parseInt(dashboard.id, 10)))); + widgets.forEach((widget) => { + delete widget.dashboardID; + delete widget.id; + }) + dashboard["widgets"] = widgets; + delete dashboard.scenarioID; + delete dashboard.id; + + return dashboard; + } + + exportDashboard(index) { + let dashboard = this.copyDashboard(index); + + // show save dialog + const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' }); + FileSaver.saveAs(blob, 'dashboard - ' + dashboard.name + '.json'); + } + + duplicateDashboard(index) { + let newDashboard = this.copyDashboard(index); + newDashboard.scenarioID = this.props.scenario.id; + newDashboard.name = newDashboard.name + '_copy'; + + AppDispatcher.dispatch({ + type: 'dashboards/start-add', + data: newDashboard, + token: this.props.sessionToken, + }); + } + + render() { + + return ( +
+ {/*Dashboard table*/} +

Dashboards + + this.setState({newDashboardModal: true})} + icon='plus' + /> + this.setState({importDashboardModal: true})} + icon='upload' + /> + +

+ + {this.props.currentUser.role === "Admin" ? + + : <> + } + + + + this.setState({ + dashboardEditModal: true, + modalDashboardData: this.props.dashboards[index] + })} + onDelete={(index) => this.setState({ + deleteDashboardModal: true, + modalDashboardData: this.props.dashboards[index], + modalDashboardIndex: index + })} + onExport={index => this.exportDashboard(index)} + onDuplicate={index => this.duplicateDashboard(index)} + /> +
+ + this.closeNewDashboardModal(data)} + /> + this.closeEditDashboardModal(data)} + /> + this.closeImportDashboardModal(data)} + /> + this.closeDeleteDashboardModal(e)} + /> + +
+ ) + } +} + +export default DashboardTable; diff --git a/src/result/result-table.js b/src/result/result-table.js new file mode 100644 index 0000000..cac8ab5 --- /dev/null +++ b/src/result/result-table.js @@ -0,0 +1,244 @@ +/** + * 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 . + ******************************************************************************/ + +import React, {Component} from "react"; +import JSZip from 'jszip'; +import {Button} from "react-bootstrap"; +import FileSaver from 'file-saver'; +import AppDispatcher from "../common/app-dispatcher"; +import IconButton from "../common/icon-button"; +import Table from "../common/table"; +import TableColumn from "../common/table-column"; +import DeleteDialog from "../common/dialogs/delete-dialog"; +import EditResultDialog from "./edit-result"; +import ResultConfigDialog from "./result-configs-dialog"; +import NewResultDialog from "./new-result"; + +class ResultTable extends Component { + + constructor() { + super(); + + this.state = { + editResultsModal: false, + modalResultsData: {}, + modalResultsIndex: 0, + newResultModal: false, + filesToDownload: [], + zipfiles: false, + resultNodl: 0, + resultConfigsModal: false, + modalResultConfigs: {}, + modalResultConfigsIndex: 0, + } + } + + componentDidUpdate(prevProps, prevState) { + // check whether file data has been loaded + if (this.state.filesToDownload && this.state.filesToDownload.length > 0) { + if (this.props.files !== prevProps.files) { + if (!this.state.zipfiles) { + let fileToDownload = this.props.files.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 = this.props.files.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 + "_" + (new Date()).toISOString(); + zip.generateAsync({ type: "blob" }).then(function (content) { + saveAs(content, zipname); + }); + this.setState({ filesToDownload: [] }); + } + } + } + } + } + + closeNewResultModal(data) { + this.setState({ newResultModal: false }); + if (data) { + data["scenarioID"] = this.props.scenario.id; + AppDispatcher.dispatch({ + type: 'results/start-add', + data, + token: this.props.sessionToken, + }); + } + } + + closeEditResultsModal() { + this.setState({ editResultsModal: false }); + } + + downloadResultData(param) { + let toDownload = []; + let zip = false; + + if (typeof (param) === 'object') { // download all files + toDownload = param.resultFileIDs; + 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 => { + AppDispatcher.dispatch({ + type: 'files/start-download', + data: fileid, + token: this.props.sessionToken + }); + }); + } + + closeDeleteResultsModal(confirmDelete) { + this.setState({ deleteResultsModal: false }); + + if (confirmDelete === false) { + return; + } + + AppDispatcher.dispatch({ + type: 'results/start-remove', + data: this.state.modalResultsData, + token: this.props.sessionToken, + }); + } + + + openResultConfigSnapshots(result) { + if (result.configSnapshots === null || result.configSnapshots === undefined) { + this.setState({ + modalResultConfigs: {"configs": []}, + modalResultConfigsIndex: result.id, + resultConfigsModal: true + }); + } else { + this.setState({ + modalResultConfigs: result.configSnapshots, + modalResultConfigsIndex: result.id, + resultConfigsModal: true + }); + } + } + + closeResultConfigSnapshots() { + this.setState({ resultConfigsModal: false }); + } + + modifyResultNoColumn(id, result) { + return + } + + render() { + + return ( +
+ {/*Result table*/} +

Results + + this.setState({ newResultModal: true })} + icon='plus' + /> + +

+ + + this.modifyResultNoColumn(id, result)} + width={70} + /> + + + + this.downloadResultData(index)} + width={300} + /> + this.setState({ editResultsModal: true, modalResultsIndex: index })} + onDownloadAll={(index) => this.downloadResultData(this.props.results[index])} + onDelete={(index) => this.setState({ deleteResultsModal: true, modalResultsData: this.props.results[index], modalResultsIndex: index })} + /> +
+ + + this.closeDeleteResultsModal(e)} + /> + + this.closeNewResultModal(data)} + /> +
+ ) + } +} + +export default ResultTable; diff --git a/src/scenario/scenario-users-table.js b/src/scenario/scenario-users-table.js new file mode 100644 index 0000000..c93b356 --- /dev/null +++ b/src/scenario/scenario-users-table.js @@ -0,0 +1,169 @@ +/** + * 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 . + ******************************************************************************/ + +import React, {Component} from "react"; +import {Button, Form, InputGroup} from "react-bootstrap"; +import {Redirect} from "react-router-dom"; +import Table from "../common/table"; +import TableColumn from "../common/table-column"; +import Icon from "../common/icon"; +import DeleteDialog from "../common/dialogs/delete-dialog"; +import AppDispatcher from "../common/app-dispatcher"; + +class ScenarioUsersTable extends Component { + + constructor(props) { + super(props); + + this.state = { + userToAdd: '', + deleteUserName: '', + deleteUserModal: false, + goToScenarios: false + } + } + + onUserInputChange(e) { + this.setState({ userToAdd: e.target.value }); + } + + addUser() { + AppDispatcher.dispatch({ + type: 'scenarios/add-user', + data: this.props.scenario.id, + username: this.state.userToAdd, + token: this.props.sessionToken + }); + + this.setState({ userToAdd: '' }); + } + + closeDeleteUserModal() { + let scenarioID = this.props.scenario.id; + if (this.state.deleteUserName === this.props.currentUser.username) { + AppDispatcher.dispatch({ + type: 'scenarios/remove-user', + data: scenarioID, + username: this.state.deleteUserName, + token: this.props.sessionToken, + ownuser: true + }); + this.setState({ goToScenarios: true }); + } else { + AppDispatcher.dispatch({ + type: 'scenarios/remove-user', + data: scenarioID, + username: this.state.deleteUserName, + token: this.props.sessionToken, + ownuser: false + }); + } + this.setState({ deleteUserModal: false }); + } + + + render() { + if (this.state.goToScenarios) { + console.log("redirect to scenario overview") + return (); + } + + const altButtonStyle = { + marginLeft: '10px', + } + + const iconStyle = { + height: '30px', + width: '30px' + } + + return ( +
+ {/*Scenario Users table*/} +

Users sharing this scenario

+ + {this.props.currentUser.role === "Admin" ? + + : <> + } + + + this.setState({ + deleteUserModal: true, + deleteUserName: this.props.scenario.users[index].username, + modalUserIndex: index + })} + /> +
+ + + this.onUserInputChange(e)} + value={this.state.userToAdd} + type="text" + /> + + + + + + +
+
+ + this.closeDeleteUserModal(c)} + /> +
+ ) + } +} + +export default ScenarioUsersTable; + diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 80f2e60..01dfd60 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -17,514 +17,86 @@ import React from 'react'; import { Container } from 'flux/utils'; -import { Button, InputGroup, Form } from 'react-bootstrap'; -import FileSaver from 'file-saver'; +import AppDispatcher from '../common/app-dispatcher'; +import IconButton from '../common/icon-button'; import ScenarioStore from './scenario-store'; import ICStore from '../ic/ic-store'; import DashboardStore from '../dashboard/dashboard-store'; import ConfigStore from '../componentconfig/config-store'; import SignalStore from '../signal/signal-store' -import AppDispatcher from '../common/app-dispatcher'; - -import Icon from '../common/icon'; -import Table from '../common/table'; -import TableColumn from '../common/table-column'; -import IconButton from '../common/icon-button'; -import ImportConfigDialog from '../componentconfig/import-config'; -import ImportDashboardDialog from "../dashboard/import-dashboard"; -import NewDashboardDialog from "../dashboard/new-dashboard"; -import EditDashboardDialog from '../dashboard/edit-dashboard'; -import EditFilesDialog from '../file/edit-files' -import NewResultDialog from '../result/new-result'; -import EditResultDialog from '../result/edit-result'; -import ResultConfigDialog from '../result/result-configs-dialog'; - - -import ICAction from '../ic/ic-action'; -import DeleteDialog from '../common/dialogs/delete-dialog'; -import EditConfigDialog from "../componentconfig/edit-config"; -import EditSignalMappingDialog from "../signal/edit-signal-mapping"; import FileStore from "../file/file-store" import WidgetStore from "../widget/widget-store"; import ResultStore from "../result/result-store" -import { Redirect } from 'react-router-dom'; -import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; -var JSZip = require("jszip"); +import DashboardTable from '../dashboard/dashboard-table' +import ResultTable from "../result/result-table"; +import ConfigTable from "../componentconfig/config-table"; +import EditFilesDialog from '../file/edit-files' +import ScenarioUsersTable from "./scenario-users-table"; + class Scenario extends React.Component { + constructor(props) { + super(props); + + this.state = { + filesEditModal: false, + filesEditSaveState: [], + } + } + + static calculateState(prevState, props){ + let scenarioID = parseInt(props.match.params.scenario, 10) + + return{ + scenario: ScenarioStore.getState().find(s => s.id === scenarioID), + results: ResultStore.getState().filter(result => result.scenarioID === scenarioID), + sessionToken: localStorage.getItem("token"), + configs: ConfigStore.getState().filter(config => config.scenarioID === scenarioID), + widgets: WidgetStore.getState(), + dashboards: DashboardStore.getState().filter(dashb => dashb.scenarioID === scenarioID), + signals: SignalStore.getState(), + currentUser: JSON.parse(localStorage.getItem("currentUser")), + files: FileStore.getState().filter(file => file.scenarioID === scenarioID), + ics: ICStore.getState(), + } + } + static getStores() { return [ScenarioStore, ConfigStore, DashboardStore, ICStore, SignalStore, FileStore, WidgetStore, ResultStore]; } - static calculateState(prevState, props) { - if (prevState == null) { - prevState = {}; - } - - // get selected scenario - const sessionToken = localStorage.getItem("token"); - - const scenario = ScenarioStore.getState().find(s => s.id === parseInt(props.match.params.scenario, 10)); - if (scenario === undefined) { - AppDispatcher.dispatch({ - type: 'scenarios/start-load', - data: props.match.params.scenario, - token: sessionToken - }); - } - - // obtain all component configurations of a scenario - let configs = ConfigStore.getState().filter(config => config.scenarioID === parseInt(props.match.params.scenario, 10)); - let editConfigModal = prevState.editConfigModal || false; - let modalConfigData = (prevState.modalConfigData !== {} && prevState.modalConfigData !== undefined) ? prevState.modalConfigData : {}; - let modalConfigIndex = 0; - - if ((typeof prevState.configs !== "undefined") && (prevState.newConfig === true) && (configs.length !== prevState.configs.length)) { - let index = configs.length - 1; - editConfigModal = true; - modalConfigData = configs[index]; - modalConfigIndex = index; - } - - let results = ResultStore.getState().filter(result => result.scenarioID === parseInt(props.match.params.scenario, 10)); - - return { - scenario, - results, - sessionToken, - configs, - editConfigModal, - modalConfigData, - modalConfigIndex, - dashboards: DashboardStore.getState().filter(dashb => dashb.scenarioID === parseInt(props.match.params.scenario, 10)), - signals: SignalStore.getState(), - currentUser: JSON.parse(localStorage.getItem("currentUser")), - files: FileStore.getState().filter(file => file.scenarioID === parseInt(props.match.params.scenario, 10)), - - ics: ICStore.getState(), - ExternalICInUse: false, - - deleteConfigModal: false, - importConfigModal: false, - newConfig: prevState.newConfig || false, - selectedConfigs: prevState.selectedConfigs || [], - filesEditModal: prevState.filesEditModal || false, - filesEditSaveState: prevState.filesEditSaveState || [], - - editResultsModal: prevState.editResultsModal || false, - modalResultsData: {}, - modalResultsIndex: prevState.modalResultsIndex, - newResultModal: false, - filesToDownload: prevState.filesToDownload, - zipfiles: prevState.zipfiles || false, - resultNodl: prevState.resultNodl, - resultConfigsModal: false, - modalResultConfigs: {}, - modalResultConfigsIndex: 0, - - editOutputSignalsModal: prevState.editOutputSignalsModal || false, - editInputSignalsModal: prevState.editInputSignalsModal || false, - - newDashboardModal: false, - dashboardEditModal: prevState.dashboardEditModal || false, - deleteDashboardModal: false, - importDashboardModal: false, - modalDashboardData: {}, - - userToAdd: '', - deleteUserName: '', - deleteUserModal: false, - goToScenarios: false - } - } - componentDidMount() { + let token = localStorage.getItem("token") + let scenarioID = parseInt(this.props.match.params.scenario, 10) //load selected scenario AppDispatcher.dispatch({ type: 'scenarios/start-load', - data: parseInt(this.props.match.params.scenario, 10), - token: this.state.sessionToken + data: scenarioID, + token: token }); - AppDispatcher.dispatch({ type: 'scenarios/start-load-users', - data: parseInt(this.props.match.params.scenario, 10), - token: this.state.sessionToken + data: scenarioID, + token: token }); // load ICs to enable that component configs and dashboards work with them AppDispatcher.dispatch({ type: 'ics/start-load', - token: this.state.sessionToken - }); - } - - componentDidUpdate(prevProps, prevState) { - // check whether file data has been loaded - 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 + "_" + (new Date()).toISOString(); - zip.generateAsync({ type: "blob" }).then(function (content) { - saveAs(content, zipname); - }); - this.setState({ filesToDownload: [] }); - } - } - } - } - } - - /* ############################################## - * User modification methods - ############################################## */ - - onUserInputChange(e) { - this.setState({ userToAdd: e.target.value }); - } - - addUser() { - AppDispatcher.dispatch({ - type: 'scenarios/add-user', - data: this.state.scenario.id, - username: this.state.userToAdd, - token: this.state.sessionToken - }); - - this.setState({ userToAdd: '' }); - } - - closeDeleteUserModal() { - let scenarioID = this.state.scenario.id; - if (this.state.deleteUserName === this.state.currentUser.username) { - AppDispatcher.dispatch({ - type: 'scenarios/remove-user', - data: scenarioID, - username: this.state.deleteUserName, - token: this.state.sessionToken, - ownuser: true - }); - this.setState({ goToScenarios: true }); - } else { - AppDispatcher.dispatch({ - type: 'scenarios/remove-user', - data: scenarioID, - username: this.state.deleteUserName, - token: this.state.sessionToken, - ownuser: false - }); - } - this.setState({ deleteUserModal: false }); - } - - /* ############################################## - * Component Configuration modification methods - ############################################## */ - - addConfig() { - const config = { - scenarioID: this.state.scenario.id, - name: 'New Component Configuration', - icID: this.state.ics.length > 0 ? this.state.ics[0].id : null, - startParameters: {}, - }; - - AppDispatcher.dispatch({ - type: 'configs/start-add', - data: config, - token: this.state.sessionToken - }); - - this.setState({ newConfig: true }); - - } - - closeEditConfigModal(data) { - this.setState({ editConfigModal: false, newConfig: false }); - - if (data) { - AppDispatcher.dispatch({ - type: 'configs/start-edit', - data: data, - token: this.state.sessionToken, - }); - } - } - - closeDeleteConfigModal(confirmDelete) { - this.setState({ deleteConfigModal: false }); - - if (confirmDelete === false) { - return; - } - - AppDispatcher.dispatch({ - type: 'configs/start-remove', - data: this.state.modalConfigData, - token: this.state.sessionToken - }); - } - - importConfig(data) { - this.setState({ importConfigModal: false }); - - if (data == null) { - return; - } - - let newConfig = JSON.parse(JSON.stringify(data.config)) - - newConfig["scenarioID"] = this.state.scenario.id; - newConfig.name = data.name; - - AppDispatcher.dispatch({ - type: 'configs/start-add', - data: newConfig, - token: this.state.sessionToken - }); - } - - copyConfig(index) { - let config = JSON.parse(JSON.stringify(this.state.configs[index])); - - let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10)))); - signals.forEach((signal) => { - delete signal.configID; - delete signal.id; - }) - - // two separate lists for inputMapping and outputMapping - let inputSignals = signals.filter(s => s.direction === 'in'); - let outputSignals = signals.filter(s => s.direction === 'out'); - - // add signal mappings to config - config["inputMapping"] = inputSignals; - config["outputMapping"] = outputSignals; - - delete config.id; - delete config.scenarioID; - delete config.inputLength; - delete config.outputLength; - - return config; - } - - exportConfig(index) { - let config = this.copyConfig(index); - - // show save dialog - const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); - FileSaver.saveAs(blob, 'config-' + config.name + '.json'); - } - - duplicateConfig(index) { - let newConfig = this.copyConfig(index); - newConfig["scenarioID"] = this.state.scenario.id; - newConfig.name = newConfig.name + '_copy'; - - AppDispatcher.dispatch({ - type: 'configs/start-add', - data: newConfig, - token: this.state.sessionToken - }); - } - - onConfigChecked(index, event) { - const selectedConfigs = Object.assign([], this.state.selectedConfigs); - for (let key in selectedConfigs) { - if (selectedConfigs[key] === index) { - // update existing entry - if (event.target.checked) { - return; - } - - selectedConfigs.splice(key, 1); - - this.setState({ selectedConfigs: selectedConfigs }); - return; - } - } - - // add new entry - if (event.target.checked === false) { - return; - } - - selectedConfigs.push(index); - this.setState({ selectedConfigs: selectedConfigs }); - } - - usesExternalIC(index) { - let icID = this.state.configs[index].icID; - - let ic = null; - for (let component of this.state.ics) { - if (component.id === icID) { - ic = component; - } - } - - if (ic == null) { - return false; - } - - if (ic.managedexternally === true) { - this.setState({ ExternalICInUse: true }) - return true - } - - return false - } - - getICName(icID) { - for (let ic of this.state.ics) { - if (ic.id === icID) { - return ic.name || ic.uuid; - } - } - } - - /* ############################################## - * Dashboard modification methods - ############################################## */ - - closeNewDashboardModal(data) { - this.setState({ newDashboardModal: false }); - if (data) { - // TODO: 'newDashboard' not used, check this - let newDashboard = data; - // add default grid value and scenarioID - newDashboard["grid"] = 15; - newDashboard["scenarioID"] = this.state.scenario.id; - - AppDispatcher.dispatch({ - type: 'dashboards/start-add', - data, - token: this.state.sessionToken, - }); - } - } - - closeEditDashboardModal(data) { - this.setState({ dashboardEditModal: false }); - - let editDashboard = this.state.modalDashboardData; - - if (data != null) { - editDashboard.name = data.name; - AppDispatcher.dispatch({ - type: 'dashboards/start-edit', - data: editDashboard, - token: this.state.sessionToken - }); - } - } - - closeDeleteDashboardModal(confirmDelete) { - this.setState({ deleteDashboardModal: false }); - - if (confirmDelete === false) { - return; - } - - AppDispatcher.dispatch({ - type: 'dashboards/start-remove', - data: this.state.modalDashboardData, - token: this.state.sessionToken, - }); - } - - closeImportDashboardModal(data) { - this.setState({ importDashboardModal: false }); - - if (data) { - let newDashboard = JSON.parse(JSON.stringify(data)); - newDashboard["scenarioID"] = this.state.scenario.id; - - AppDispatcher.dispatch({ - type: 'dashboards/start-add', - data: newDashboard, - token: this.state.sessionToken, - }); - } - } - - copyDashboard(index) { - let dashboard = JSON.parse(JSON.stringify(this.state.dashboards[index])); - - let widgets = JSON.parse(JSON.stringify(WidgetStore.getState().filter(w => w.dashboardID === parseInt(dashboard.id, 10)))); - widgets.forEach((widget) => { - delete widget.dashboardID; - delete widget.id; - }) - dashboard["widgets"] = widgets; - delete dashboard.scenarioID; - delete dashboard.id; - - return dashboard; - } - - exportDashboard(index) { - let dashboard = this.copyDashboard(index); - - // show save dialog - const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' }); - FileSaver.saveAs(blob, 'dashboard - ' + dashboard.name + '.json'); - } - - duplicateDashboard(index) { - let newDashboard = this.copyDashboard(index); - newDashboard.scenarioID = this.state.scenario.id; - newDashboard.name = newDashboard.name + '_copy'; - - AppDispatcher.dispatch({ - type: 'dashboards/start-add', - data: newDashboard, - token: this.state.sessionToken, + token: token }); } /* ############################################## - * Signal modification methods + * File modification methods ############################################## */ - closeEditSignalsModal(direction) { - - // reload the config - AppDispatcher.dispatch({ - type: 'configs/start-load', - data: this.state.modalConfigData.id, - token: this.state.sessionToken - }); - - if (direction === "in") { - this.setState({ editInputSignalsModal: false }); - } else if (direction === "out") { - this.setState({ editOutputSignalsModal: false }); - } - - - } - onEditFiles() { let tempFiles = []; this.state.files.forEach(file => { @@ -538,199 +110,18 @@ class Scenario extends React.Component { closeEditFiles() { this.setState({ filesEditModal: false }); - // TODO do we need this if the dispatches happen in the dialog? } - signalsAutoConf(index) { - let componentConfig = this.state.configs[index]; - // determine apiurl of infrastructure component - let ic = this.state.ics.find(ic => ic.id === componentConfig.icID) - if (!ic.type.includes("villas-node")) { - let message = "Cannot autoconfigure signals for IC type " + ic.type + " of category " + ic.category + ". This is only possible for gateway ICs of type 'VILLASnode'." - console.warn(message); - - const SIGNAL_AUTOCONF_WARN_NOTIFICATION = { - title: 'Failed to load signal config for IC ' + ic.name, - message: message, - level: 'warning' - }; - NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_WARN_NOTIFICATION); - return; - } - - let splitWebsocketURL = ic.websocketurl.split("/") - - AppDispatcher.dispatch({ - type: 'signals/start-autoconfig', - url: ic.apiurl + "/nodes", - socketname: splitWebsocketURL[splitWebsocketURL.length - 1], - token: this.state.sessionToken, - configID: componentConfig.id - }); - - } - - uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - // eslint-disable-next-line - var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - /* ############################################## - * File modification methods - ############################################## */ - - getListOfFiles(files, fileIDs) { - let fileList = []; - - for (let id of fileIDs) { - for (let file of files) { - if (file.id === id) { - fileList.push(file.name) - } - } - } - - return fileList.join(';'); - } - - /* ############################################## - * Result modification methods - ############################################## */ - - closeNewResultModal(data) { - this.setState({ newResultModal: false }); - if (data) { - data["scenarioID"] = this.state.scenario.id; - AppDispatcher.dispatch({ - type: 'results/start-add', - data, - token: this.state.sessionToken, - }); - } - } - - closeEditResultsModal() { - this.setState({ editResultsModal: false }); - } - - downloadResultData(param) { - let toDownload = []; - let zip = false; - - if (typeof (param) === 'object') { // download all files - toDownload = param.resultFileIDs; - 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 => { - AppDispatcher.dispatch({ - type: 'files/start-download', - data: fileid, - token: this.state.sessionToken - }); - }); - } - - closeDeleteResultsModal(confirmDelete) { - this.setState({ deleteResultsModal: false }); - - if (confirmDelete === false) { - return; - } - - AppDispatcher.dispatch({ - type: 'results/start-remove', - data: this.state.modalResultsData, - token: this.state.sessionToken, - }); - } - - openResultConfigSnaphots(result) { - if (result.configSnapshots === null || result.configSnapshots === undefined) { - this.setState({ - modalResultConfigs: {"configs": []}, - modalResultConfigsIndex: result.id, - resultConfigsModal: true - }); - } else { - this.setState({ - modalResultConfigs: result.configSnapshots, - modalResultConfigsIndex: result.id, - resultConfigsModal: true - }); - } - } - - closeResultConfigSnapshots() { - this.setState({ resultConfigsModal: false }); - } - - modifyResultNoColumn(id, result) { - return - } - - startPintura(configIndex) { - let config = this.state.configs[configIndex]; - - // get xml / CIM file - let files = [] - for (let id of config.fileIDs) { - for (let file of this.state.files) { - if (file.id === id && ['xml'].some(e => file.type.includes(e))) { - files.push(file); - } - } - } - - if (files.length > 1) { - // more than one CIM file... - console.warn("There is more than one CIM file selected in this component configuration. I will open them all in a separate tab.") - } - - let baseURL = 'aaa.bbb.ccc.ddd/api/v2/files/' - for (let file of files) { - // endpoint param serves for download and upload of CIM file, token is required for authentication - let params = { - token: this.state.sessionToken, - endpoint: baseURL + String(file.id), - } - - // TODO start Pintura for editing CIM/ XML file from here - console.warn("Starting Pintura... and nothing happens so far :-) ", params) - } - } - - /* ############################################## * Render method ############################################## */ render() { - if (this.state.goToScenarios) { - console.log("redirect to scenario overview") - return (); - } - - const altButtonStyle = { - marginLeft: '10px', - } const tableHeadingStyle = { paddingTop: '30px' } - const iconStyle = { - height: '30px', - width: '30px' - } - if (this.state.scenario === undefined) { return

Loading Scenario...

; } @@ -755,362 +146,42 @@ class Scenario extends React.Component { scenarioID={this.state.scenario.id} /> - {/*Component Configurations table*/} -

Component Configurations - - this.addConfig()} - icon='plus' - /> - this.setState({ importConfigModal: true })} - icon='upload' - /> - -

- - !this.usesExternalIC(index)} - onChecked={(index, event) => this.onConfigChecked(index, event)} - width={20} - /> - {this.state.currentUser.role === "Admin" ? - - : <> - } - - this.setState({ editOutputSignalsModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} - width={150} - /> - this.setState({ editInputSignalsModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} - width={150} - /> - this.signalsAutoConf(index)} - width={150} - /> - this.getICName(icID)} - width={300} - /> - this.setState({ editConfigModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} - onDelete={(index) => this.setState({ deleteConfigModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} - onExport={index => this.exportConfig(index)} - onDuplicate={index => this.duplicateConfig(index)} - /> -
- - {this.state.ExternalICInUse ? -
- this.copyConfig(index)} - token = {this.state.sessionToken} - actions={[ - { 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' } } - ]} /> -
- :
- } - -
- - this.closeEditConfigModal(data)} - config={this.state.modalConfigData} - ics={this.state.ics} + - this.importConfig(data)} ics={this.state.ics} - /> - this.closeDeleteConfigModal(c)} - /> - this.closeEditSignalsModal(direction)} - direction="Output" signals={this.state.signals} - configID={this.state.modalConfigData.id} + scenario={this.state.scenario} sessionToken={this.state.sessionToken} + currentUser={this.state.currentUser} + tableHeadingStyle={tableHeadingStyle} /> - this.closeEditSignalsModal(direction)} - direction="Input" - signals={this.state.signals} - configID={this.state.modalConfigData.id} + + - {/*Dashboard table*/} -

Dashboards - - this.setState({ newDashboardModal: true })} - icon='plus' - /> - this.setState({ importDashboardModal: true })} - icon='upload' - /> - -

- - {this.state.currentUser.role === "Admin" ? - - : <> - } - - - - this.setState({ dashboardEditModal: true, modalDashboardData: this.state.dashboards[index] })} - onDelete={(index) => this.setState({ deleteDashboardModal: true, modalDashboardData: this.state.dashboards[index], modalDashboardIndex: index })} - onExport={index => this.exportDashboard(index)} - onDuplicate={index => this.duplicateDashboard(index)} - /> -
- - this.closeNewDashboardModal(data)} - /> - this.closeEditDashboardModal(data)} - /> - this.closeImportDashboardModal(data)} - /> - this.closeDeleteDashboardModal(e)} - /> - - {/*Result table*/} -

Results - - this.setState({ newResultModal: true })} - icon='plus' - /> - -

- - - this.modifyResultNoColumn(id, result)} - width={70} - /> - - - - this.downloadResultData(index)} - width={300} - /> - 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 })} - /> -
- - - this.closeDeleteResultsModal(e)} - /> - - this.closeNewResultModal(data)} + files={this.state.files} + scenario={this.state.scenario} + sessionToken={this.state.sessionToken} + tableHeadingStyle={tableHeadingStyle} /> - {/*Scenario Users table*/} -

Users sharing this scenario

- - {this.state.currentUser.role === "Admin" ? - - : <> - } - - - this.setState({ - deleteUserModal: true, - deleteUserName: this.state.scenario.users[index].username, - modalUserIndex: index - })} - /> -
- - - this.onUserInputChange(e)} - value={this.state.userToAdd} - type="text" - /> - - - - - - -
-
- - this.closeDeleteUserModal(c)} + -
; + +
} }