mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'master' into react-update
# Conflicts: # package-lock.json # package.json # src/app.js # src/dashboard/dashboard.js
This commit is contained in:
commit
b39db51275
14 changed files with 1337 additions and 1024 deletions
150
package-lock.json
generated
150
package-lock.json
generated
|
@ -3910,6 +3910,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",
|
||||
|
@ -5735,6 +5740,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",
|
||||
|
@ -8141,11 +8154,6 @@
|
|||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
|
||||
},
|
||||
"immer": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz",
|
||||
"integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA=="
|
||||
},
|
||||
"immutable": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
|
||||
|
@ -10571,6 +10579,30 @@
|
|||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
@ -10602,6 +10634,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"
|
||||
}
|
||||
},
|
||||
"killable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||
|
@ -10754,11 +10805,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",
|
||||
|
@ -13664,9 +13750,9 @@
|
|||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.3.tgz",
|
||||
"integrity": "sha512-4lEA5gF4OHrcJLMUV1t+4XbNDiJbsAWCH5Z2uqlTqW6dD7Cf5nEASkeXrCI/Mz83sI2o527oBIFKVMXtRf1Vtg==",
|
||||
"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",
|
||||
|
@ -13728,11 +13814,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",
|
||||
|
@ -13827,11 +13923,6 @@
|
|||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"react-fullscreenable": {
|
||||
"version": "2.5.1-0",
|
||||
"resolved": "https://registry.npmjs.org/react-fullscreenable/-/react-fullscreenable-2.5.1-0.tgz",
|
||||
|
@ -13887,6 +13978,39 @@
|
|||
"react-base16-styling": "^0.6.0",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"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": {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"handlebars": "^4.7.7",
|
||||
"jquery": "^3.6.0",
|
||||
"jszip": "^3.6.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
|
@ -60,7 +61,7 @@
|
|||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"proxy": "http://localhost:4000",
|
||||
"proxy": "https://villas.k8s.eonerc.rwth-aachen.de",
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
|
|
34
src/app.js
34
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';
|
||||
|
@ -39,6 +39,7 @@ import User from './user/user';
|
|||
import APIBrowser from './common/api-browser';
|
||||
import LoginStore from './user/login-store'
|
||||
|
||||
|
||||
import './styles/app.css';
|
||||
|
||||
class App extends React.Component {
|
||||
|
@ -63,22 +64,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("Logged-in as user ", currentUser.username)
|
||||
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("Logged-in as user ", currentUser.username)
|
||||
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 (<Redirect to="/logout" />);
|
||||
}
|
||||
|
|
434
src/componentconfig/config-table.js
Normal file
434
src/componentconfig/config-table.js
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
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 (
|
||||
<div>
|
||||
{/*Component Configurations table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Component Configurations
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
key={0}
|
||||
tooltip='Add Component Configuration'
|
||||
onClick={() => this.addConfig()}
|
||||
icon='plus'
|
||||
/>
|
||||
<IconButton
|
||||
key={1}
|
||||
tooltip='Import Component Configuration'
|
||||
onClick={() => this.setState({ importConfigModal: true })}
|
||||
icon='upload'
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
<Table data={this.props.configs}>
|
||||
<TableColumn
|
||||
checkbox
|
||||
checkboxDisabled={(index) => !this.usesExternalIC(index)}
|
||||
onChecked={(index, event) => this.onConfigChecked(index, event)}
|
||||
width={20}
|
||||
/>
|
||||
{this.props.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
width={70}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='name'
|
||||
width={250}
|
||||
/>
|
||||
<TableColumn
|
||||
title='# Output Signals'
|
||||
dataKey='outputLength'
|
||||
editButton
|
||||
onEdit={index => this.setState({ editOutputSignalsModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })}
|
||||
width={150}
|
||||
/>
|
||||
<TableColumn
|
||||
title='# Input Signals'
|
||||
dataKey='inputLength'
|
||||
editButton
|
||||
onEdit={index => this.setState({ editInputSignalsModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })}
|
||||
width={150}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Import Signals'
|
||||
exportButton
|
||||
onExport={(index) => this.signalsAutoConf(index)}
|
||||
width={150}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Infrastructure Component'
|
||||
dataKey='icID'
|
||||
modifier={(icID) => this.getICName(icID)}
|
||||
width={200}
|
||||
/>
|
||||
<TableColumn
|
||||
title=''
|
||||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
duplicateButton
|
||||
onEdit={index => 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)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
{this.state.ExternalICInUse ?
|
||||
<div style={{ float: 'left' }}>
|
||||
<ICAction
|
||||
ics={this.props.ics}
|
||||
configs={this.props.configs}
|
||||
selectedConfigs = {this.state.selectedConfigs}
|
||||
snapshotConfig = {(index) => 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' } }
|
||||
]} />
|
||||
</div>
|
||||
: <div />
|
||||
}
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<EditConfigDialog
|
||||
show={this.state.editConfigModal}
|
||||
onClose={data => this.closeEditConfigModal(data)}
|
||||
config={this.state.modalConfigData}
|
||||
ics={this.props.ics}
|
||||
files={this.props.files}
|
||||
sessionToken={this.props.sessionToken}
|
||||
/>
|
||||
<ImportConfigDialog
|
||||
show={this.state.importConfigModal}
|
||||
onClose={data => this.importConfig(data)}
|
||||
ics={this.props.ics}
|
||||
/>
|
||||
<DeleteDialog
|
||||
title="component configuration"
|
||||
name={this.state.modalConfigData.name}
|
||||
show={this.state.deleteConfigModal}
|
||||
onClose={(c) => this.closeDeleteConfigModal(c)}
|
||||
/>
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editOutputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Output"
|
||||
signals={this.props.signals}
|
||||
configID={this.state.modalConfigData.id}
|
||||
sessionToken={this.props.sessionToken}
|
||||
/>
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editInputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Input"
|
||||
signals={this.props.signals}
|
||||
configID={this.state.modalConfigData.id}
|
||||
sessionToken={this.props.sessionToken}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigTable;
|
229
src/dashboard/dashboard-table.js
Normal file
229
src/dashboard/dashboard-table.js
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
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 (
|
||||
<div>
|
||||
{/*Dashboard table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Dashboards
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
key={0}
|
||||
tooltip='Add Dashboard'
|
||||
onClick={() => this.setState({newDashboardModal: true})}
|
||||
icon='plus'
|
||||
/>
|
||||
<IconButton
|
||||
key={1}
|
||||
tooltip='Import Dashboard'
|
||||
onClick={() => this.setState({importDashboardModal: true})}
|
||||
icon='upload'
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
<Table data={this.props.dashboards}>
|
||||
{this.props.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
width={70}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='name'
|
||||
link='/dashboards/'
|
||||
linkKey='id'
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Grid'
|
||||
dataKey='grid'
|
||||
width={100}
|
||||
/>
|
||||
|
||||
<TableColumn
|
||||
title=''
|
||||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
duplicateButton
|
||||
onEdit={index => 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)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<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)}
|
||||
/>
|
||||
<DeleteDialog
|
||||
title="dashboard"
|
||||
name={this.state.modalDashboardData.name}
|
||||
show={this.state.deleteDashboardModal}
|
||||
onClose={(e) => this.closeDeleteDashboardModal(e)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardTable;
|
|
@ -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}
|
||||
/>
|
||||
|
||||
<EditFiles
|
||||
<EditFilesDialog
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.filesEditModal}
|
||||
onClose={this.closeEditFiles.bind(this)}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Form, Button, Col, ProgressBar } from 'react-bootstrap';
|
||||
import {Form, Button, Col, ProgressBar, Row} from 'react-bootstrap';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import Table from "../common/table";
|
||||
|
@ -142,24 +142,26 @@ class EditFilesDialog extends React.Component {
|
|||
/>
|
||||
</Table>
|
||||
|
||||
<Form.Group as={Col} >
|
||||
<Form.Control
|
||||
disabled={this.props.disabled}
|
||||
type='file'
|
||||
onChange={(event) => this.selectUploadFile(event)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group as={Col} >
|
||||
<div style={{ float: 'center' }}>
|
||||
<h5>Add file</h5>
|
||||
<Row>
|
||||
<Col xs lg="4">
|
||||
<Form.Control type='file' onChange={(event) => this.selectUploadFile(event)} />
|
||||
</Col>
|
||||
<Col xs lg="2">
|
||||
<span className='solid-button'>
|
||||
<Button
|
||||
variant='secondary'
|
||||
disabled={this.state.uploadFile === null}
|
||||
onClick={() => this.startFileUpload()}>
|
||||
Upload
|
||||
</Button>
|
||||
</Button>
|
||||
</span>
|
||||
</Form.Group>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<Form.Group as={Col} >
|
||||
<ProgressBar
|
||||
|
@ -167,7 +169,6 @@ class EditFilesDialog extends React.Component {
|
|||
animated={true}
|
||||
now={this.state.uploadProgress}
|
||||
label={this.state.uploadProgress + '%'}
|
||||
style={progressBarStyle}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -154,11 +154,13 @@ class EditResultDialog extends React.Component {
|
|||
<Form.Control.Feedback />
|
||||
</Col>
|
||||
<Col xs lg="2">
|
||||
<span className='solid-button'>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={() => this.submitDescription()}>
|
||||
Save
|
||||
</Button>
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Group>
|
||||
|
@ -194,11 +196,14 @@ class EditResultDialog extends React.Component {
|
|||
<Form.Control type='file' onChange={(event) => this.selectUploadFile(event)} />
|
||||
</Col>
|
||||
<Col xs lg="2">
|
||||
<span className='solid-button'>
|
||||
<Button
|
||||
variant='secondary'
|
||||
disabled={this.state.uploadFile === null}
|
||||
onClick={() => this.startFileUpload()}>
|
||||
Upload
|
||||
</Button>
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
|
244
src/result/result-table.js
Normal file
244
src/result/result-table.js
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
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 <Button variant="link" style={{ color: '#047cab' }} onClick={() => this.openResultConfigSnapshots(result)}>{id}</Button>
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/*Result table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Results
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
key={1}
|
||||
tooltip='Add Result'
|
||||
onClick={() => this.setState({ newResultModal: true })}
|
||||
icon='plus'
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<Table data={this.props.results}>
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
modifier={(id, result) => this.modifyResultNoColumn(id, result)}
|
||||
width={70}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Description'
|
||||
dataKey='description'
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Created at'
|
||||
dataKey='createdAt'
|
||||
width={200}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Last update'
|
||||
dataKey='updatedAt'
|
||||
width={200}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Files'
|
||||
dataKey='resultFileIDs'
|
||||
linkKey='filebuttons'
|
||||
data={this.props.files}
|
||||
onDownload={(index) => this.downloadResultData(index)}
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
downloadAllButton
|
||||
deleteButton
|
||||
onEdit={index => 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 })}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<EditResultDialog
|
||||
sessionToken={this.props.sessionToken}
|
||||
show={this.state.editResultsModal}
|
||||
files={this.props.files}
|
||||
results={this.props.results}
|
||||
resultId={this.state.modalResultsIndex}
|
||||
scenarioID={this.props.scenario.id}
|
||||
onClose={this.closeEditResultsModal.bind(this)}
|
||||
/>
|
||||
<DeleteDialog
|
||||
title="result"
|
||||
name={this.state.modalResultsData.id}
|
||||
show={this.state.deleteResultsModal}
|
||||
onClose={(e) => this.closeDeleteResultsModal(e)}
|
||||
/>
|
||||
<ResultConfigDialog
|
||||
show={this.state.resultConfigsModal}
|
||||
configs={this.state.modalResultConfigs}
|
||||
resultNo={this.state.modalResultConfigsIndex}
|
||||
onClose={this.closeResultConfigSnapshots.bind(this)}
|
||||
/>
|
||||
<NewResultDialog
|
||||
show={this.state.newResultModal}
|
||||
onClose={data => this.closeNewResultModal(data)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ResultTable;
|
169
src/scenario/scenario-users-table.js
Normal file
169
src/scenario/scenario-users-table.js
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
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 (<Redirect to="/scenarios" />);
|
||||
}
|
||||
|
||||
const altButtonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/*Scenario Users table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Users sharing this scenario</h2>
|
||||
<Table data={this.props.scenario.users}>
|
||||
{this.props.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
width={70}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='username'
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Role'
|
||||
dataKey='role'
|
||||
width={100}
|
||||
/>
|
||||
<TableColumn
|
||||
title=''
|
||||
width={30}
|
||||
align='right'
|
||||
deleteButton
|
||||
onDelete={(index) => this.setState({
|
||||
deleteUserModal: true,
|
||||
deleteUserName: this.props.scenario.users[index].username,
|
||||
modalUserIndex: index
|
||||
})}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<InputGroup
|
||||
style={{
|
||||
width: 400,
|
||||
float: 'right'
|
||||
}}
|
||||
>
|
||||
<Form.Control
|
||||
placeholder="Username"
|
||||
onChange={(e) => this.onUserInputChange(e)}
|
||||
value={this.state.userToAdd}
|
||||
type="text"
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<span className='icon-button'>
|
||||
<Button
|
||||
variant='light'
|
||||
type="submit"
|
||||
style={altButtonStyle}
|
||||
onClick={() => this.addUser()}>
|
||||
<Icon icon="plus" classname={'icon-color'} style={iconStyle} />
|
||||
</Button>
|
||||
</span>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<DeleteDialog
|
||||
title="Delete user from scenario"
|
||||
name={this.state.deleteUserName}
|
||||
show={this.state.deleteUserModal}
|
||||
onClose={(c) => this.closeDeleteUserModal(c)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ScenarioUsersTable;
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -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 (<Redirect to="/home" />);
|
||||
}
|
||||
else if (this.state.secondsToWait == 0) {
|
||||
else if (this.state.secondsToWait === 0) {
|
||||
this.stopTimer();
|
||||
return (<Redirect to="/login" />);
|
||||
} else {
|
||||
return <div class="verticalhorizontal">
|
||||
return <div className="verticalhorizontal">
|
||||
<img
|
||||
style={{height: 300}}
|
||||
src={require('../img/dog-waiting-bw.jpg').default}
|
||||
|
|
Loading…
Add table
Reference in a new issue