mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'new-api' into develop
This commit is contained in:
commit
7954949481
136 changed files with 4784 additions and 2993 deletions
2653
package-lock.json
generated
2653
package-lock.json
generated
File diff suppressed because it is too large
Load diff
69
package.json
69
package.json
|
@ -3,47 +3,52 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.8",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.5.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.19",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.9.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bootstrap": "^3.3.7",
|
||||
"bootstrap": "^4.3.1",
|
||||
"classnames": "^2.2.6",
|
||||
"d3-array": "^1.2.4",
|
||||
"d3-array": "^2.2.0",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-scale": "^1.0.6",
|
||||
"d3-selection": "^1.3.2",
|
||||
"d3-shape": "^1.2.2",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-scale-chromatic": "^1.3.3",
|
||||
"d3-selection": "^1.4.0",
|
||||
"d3-shape": "^1.3.5",
|
||||
"d3-time-format": "^2.1.3",
|
||||
"es6-promise": "^4.2.5",
|
||||
"file-saver": "^1.3.8",
|
||||
"flux": "^3.1.2",
|
||||
"gaugeJS": "^1.3.2",
|
||||
"handlebars": "^4.1.1",
|
||||
"immutable": "^3.8.1",
|
||||
"jszip": "^3.2.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"file-saver": "^2.0.2",
|
||||
"flux": "^3.1.3",
|
||||
"frontend-collective-react-dnd-scrollzone": "^1.0.2",
|
||||
"gaugeJS": "^1.3.7",
|
||||
"handlebars": "^4.5.1",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"jquery": "^3.4.1",
|
||||
"jszip": "^3.2.2",
|
||||
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
|
||||
"lodash": "^4.17.11",
|
||||
"prop-types": "^15.6.2",
|
||||
"rc-slider": "^8.6.3",
|
||||
"react": "^16.6.3",
|
||||
"react-bootstrap": "^0.31.1",
|
||||
"react-contexify": "^3.0.3",
|
||||
"lodash": "^4.17.15",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-slider": "^8.6.13",
|
||||
"react": "^16.8.6",
|
||||
"react-bootstrap": "^1.0.0-beta.9",
|
||||
"react-contexify": "^4.1.1",
|
||||
"react-d3": "^0.4.0",
|
||||
"react-dnd": "^2.6.0",
|
||||
"react-dnd-html5-backend": "^2.6.0",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-fullscreenable": "^2.5.0",
|
||||
"react-dnd": "^9.3.2",
|
||||
"react-dnd-html5-backend": "^9.3.2",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-fullscreenable": "^2.5.1-0",
|
||||
"react-grid-system": "^4.4.10",
|
||||
"react-json-view": "^1.19.1",
|
||||
"react-notification-system": "^0.2.17",
|
||||
"react-rnd": "^7.4.3",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-rnd": "^10.0.0",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "^3.0.1",
|
||||
"react-sortable-tree": "^0.1.19",
|
||||
"react-svg-pan-zoom": "^2.18.0",
|
||||
"superagent": "^3.8.3",
|
||||
"validator": "^10.9.0"
|
||||
"react-sortable-tree": "^2.6.2",
|
||||
"react-svg-pan-zoom": "^3.1.0",
|
||||
"superagent": "^5.1.0",
|
||||
"typescript": "^3.5.3",
|
||||
"validator": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0"
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
|
||||
import { expect } from 'chai';
|
||||
|
||||
import createControls from '../../../components/dialogs/edit-widget-control-creator';
|
||||
import EditWidgetTextControl from '../../../components/dialogs/edit-widget-text-control';
|
||||
import EditWidgetColorControl from '../../../components/dialogs/edit-widget-color-control';
|
||||
import EditWidgetTimeControl from '../../../components/dialogs/edit-widget-time-control';
|
||||
import EditImageWidgetControl from '../../../components/dialogs/edit-widget-image-control';
|
||||
import EditWidgetSimulationControl from '../../../components/dialogs/edit-widget-simulation-control';
|
||||
import EditWidgetSignalControl from '../../../components/dialogs/edit-widget-signal-control';
|
||||
import EditWidgetSignalsControl from '../../../components/dialogs/edit-widget-signals-control';
|
||||
import EditWidgetOrientation from '../../../components/dialogs/edit-widget-orientation';
|
||||
import EditWidgetTextSizeControl from '../../../components/dialogs/edit-widget-text-size-control';
|
||||
import EditWidgetAspectControl from '../../../components/dialogs/edit-widget-aspect-control';
|
||||
import EditWidgetCheckboxControl from '../../../components/dialogs/edit-widget-checkbox-control';
|
||||
import EditWidgetMinMaxControl from '../../../components/dialogs/edit-widget-min-max-control';
|
||||
import EditWidgetColorZonesControl from '../../../components/dialogs/edit-widget-color-zones-control';
|
||||
import EditWidgetHTMLContent from '../../../components/dialogs/edit-widget-html-content';
|
||||
import EditWidgetNumberControl from '../../../components/dialogs/edit-widget-number-control';
|
||||
import createControls from '../../widget/edit-widget-control-creator';
|
||||
import EditWidgetTextControl from '../../widget/edit-widget-text-control';
|
||||
import EditWidgetColorControl from '../../widget/edit-widget-color-control';
|
||||
import EditWidgetTimeControl from '../../widget/edit-widget-time-control';
|
||||
import EditImageWidgetControl from '../../widget/edit-widget-image-control';
|
||||
import EditWidgetSimulationControl from '../../widget/edit-widget-simulation-control';
|
||||
import EditWidgetSignalControl from '../../widget/edit-widget-signal-control';
|
||||
import EditWidgetSignalsControl from '../../widget/edit-widget-signals-control';
|
||||
import EditWidgetOrientation from '../../widget/edit-widget-orientation';
|
||||
import EditWidgetTextSizeControl from '../../widget/edit-widget-text-size-control';
|
||||
import EditWidgetAspectControl from '../../widget/edit-widget-aspect-control';
|
||||
import EditWidgetCheckboxControl from '../../widget/edit-widget-checkbox-control';
|
||||
import EditWidgetMinMaxControl from '../../widget/edit-widget-min-max-control';
|
||||
import EditWidgetColorZonesControl from '../../widget/edit-widget-color-zones-control';
|
||||
import EditWidgetHTMLContent from '../../widget/edit-widget-html-content';
|
||||
import EditWidgetNumberControl from '../../widget/edit-widget-number-control';
|
||||
|
||||
describe('edit widget control creator', () => {
|
||||
it('should not return null', () => {
|
174
src/app.js
Normal file
174
src/app.js
Normal file
|
@ -0,0 +1,174 @@
|
|||
/**
|
||||
* File: app.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
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 { Col } from 'react-bootstrap';
|
||||
import { Hidden } from 'react-grid-system'
|
||||
|
||||
import AppDispatcher from './common/app-dispatcher';
|
||||
import ScenarioStore from './scenario/scenario-store';
|
||||
import SimulatorStore from './simulator/simulator-store';
|
||||
import UserStore from './user/user-store';
|
||||
import NotificationsDataManager from './common/data-managers/notifications-data-manager';
|
||||
|
||||
import Home from './common/home';
|
||||
import Header from './common/header';
|
||||
import Footer from './common/footer';
|
||||
import SidebarMenu from './common/menu-sidebar';
|
||||
import HeaderMenu from './common/header-menu';
|
||||
|
||||
//import Projects from './project/projects';
|
||||
//import Project from './project/project';
|
||||
import Simulators from './simulator/simulators';
|
||||
import Dashboard from './dashboard/dashboard';
|
||||
//import Simulations from './simulation/simulations';
|
||||
//import Simulation from './simulation/simulation';
|
||||
import Scenarios from './scenario/scenarios';
|
||||
import Scenario from './scenario/scenario';
|
||||
import SimulationModel from './simulationmodel/simulation-model';
|
||||
import Users from './user/users';
|
||||
import User from './user/user';
|
||||
|
||||
import './styles/app.css';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
static getStores() {
|
||||
return [ SimulatorStore, UserStore, ScenarioStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState) {
|
||||
let currentUser = UserStore.getState().currentUser;
|
||||
|
||||
return {
|
||||
simulators: SimulatorStore.getState(),
|
||||
scenarios: ScenarioStore.getState(),
|
||||
currentRole: currentUser ? currentUser.role : '',
|
||||
currentUsername: currentUser ? currentUser.username: '',
|
||||
currentUserID: UserStore.getState().userid,
|
||||
token: UserStore.getState().token,
|
||||
|
||||
showSidebarMenu: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// if token stored locally, request user
|
||||
const token = localStorage.getItem('token');
|
||||
const userid = localStorage.getItem('userid');
|
||||
|
||||
if (token != null && token !== '') {
|
||||
// save token so we dont logout
|
||||
this.setState({ token });
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token,
|
||||
userid: userid
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// load all simulators and scenarios to fetch data
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.token
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
token: this.state.token
|
||||
});
|
||||
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
}
|
||||
|
||||
showSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: true });
|
||||
};
|
||||
|
||||
hideSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.token == null) {
|
||||
return (<Redirect to="/login" />);
|
||||
}
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend} >
|
||||
<div>
|
||||
{/*
|
||||
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} smHidden mdHidden lgHidden className="sidenav">
|
||||
*/}
|
||||
<Hidden sm md lg xl>
|
||||
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} className="sidenav">
|
||||
<HeaderMenu onClose={this.hideSidebarMenu} currentRole={this.state.currentRole} />
|
||||
</Col>
|
||||
</Hidden>
|
||||
|
||||
<div className="app">
|
||||
<NotificationSystem ref="notificationSystem" />
|
||||
|
||||
<Header onMenuButton={this.showSidebarMenu} showMenuButton={false} />
|
||||
|
||||
<div className={`app-body app-body-spacing`} >
|
||||
<Col xs={false}>
|
||||
<SidebarMenu currentRole={this.state.currentRole} />
|
||||
</Col>
|
||||
|
||||
<div className={`app-content app-content-margin-left`}>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/home" component={Home} />
|
||||
<Route path="/dashboards/:dashboard" component={Dashboard} />
|
||||
<Route exact path="/scenarios" component={Scenarios} />
|
||||
<Route path="/scenarios/:scenario" component={Scenario} />
|
||||
<Route path="/simulationModel/:simulationModel" component={SimulationModel} />
|
||||
<Route path="/simulators" component={Simulators} />
|
||||
<Route path="/user" component={User} />
|
||||
<Route path="/users" component={Users} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</DndProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Removed routes
|
||||
//<Route exact path="/projects" component={Projects} />
|
||||
//<Route path="/projects/:project" component={Project} />
|
||||
//<Route exact path="/simulations" component={Simulations} />
|
||||
//<Route path="/simulations/:simulation" component={Simulation} />
|
||||
|
||||
let fluxContainerConverter = require('./common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(App));
|
||||
//DragDropContext(HTML5Backend)(Container.create(App));
|
15
src/common/FluxContainerConverter.js
Normal file
15
src/common/FluxContainerConverter.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/// FluxContainerConverter.js
|
||||
/// This is an ugly workaround found here https://github.com/facebook/flux/issues/351 to make Flux Containers work with ES6
|
||||
|
||||
module.exports = {
|
||||
convert: function(containerClass) {
|
||||
const tmp = containerClass;
|
||||
containerClass = function(...args) {
|
||||
return new tmp(...args);
|
||||
};
|
||||
containerClass.prototype = tmp.prototype;
|
||||
containerClass.getStores = tmp.getStores;
|
||||
containerClass.calculateState = tmp.calculateState;
|
||||
return containerClass;
|
||||
}
|
||||
};
|
|
@ -58,7 +58,8 @@ class RestAPI {
|
|||
var req = request.get(url);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
|
@ -76,7 +77,7 @@ class RestAPI {
|
|||
var req = request.post(url).send(body).timeout({ response: 5000 }); // Simple response start timeout (3s)
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
|
@ -97,7 +98,7 @@ class RestAPI {
|
|||
var req = request.delete(url);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
|
@ -115,7 +116,7 @@ class RestAPI {
|
|||
var req = request.put(url).send(body);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
|
@ -133,7 +134,7 @@ class RestAPI {
|
|||
const req = request.post(url).send(data).on('progress', progressCallback);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
|
@ -21,7 +21,8 @@
|
|||
|
||||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import AppDispatcher from './app-dispatcher';
|
||||
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
|
||||
|
||||
class ArrayStore extends ReduceStore {
|
||||
constructor(type, dataManager) {
|
||||
|
@ -39,7 +40,7 @@ class ArrayStore extends ReduceStore {
|
|||
// search for existing element to update
|
||||
state.forEach((element, index, array) => {
|
||||
newElements = newElements.filter((updateElement, newIndex) => {
|
||||
if (element._id === updateElement._id) {
|
||||
if (element.id === updateElement.id) {
|
||||
// update each property
|
||||
for (var key in updateElement) {
|
||||
if (updateElement.hasOwnProperty(key)) {
|
||||
|
@ -67,6 +68,7 @@ class ArrayStore extends ReduceStore {
|
|||
reduce(state, action) {
|
||||
switch (action.type) {
|
||||
case this.type + '/start-load':
|
||||
|
||||
if (Array.isArray(action.data)) {
|
||||
action.data.forEach((id) => {
|
||||
this.dataManager.load(id, action.token);
|
||||
|
@ -84,9 +86,18 @@ class ArrayStore extends ReduceStore {
|
|||
}
|
||||
|
||||
case this.type + '/load-error':
|
||||
// TODO: Add error message
|
||||
return state;
|
||||
|
||||
if (action.error && !action.error.handled && action.error.response) {
|
||||
|
||||
const USER_LOAD_ERROR_NOTIFICATION = {
|
||||
title: 'Failed to load',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_LOAD_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case this.type + '/start-add':
|
||||
this.dataManager.add(action.data, action.token);
|
||||
return state;
|
||||
|
@ -95,8 +106,9 @@ class ArrayStore extends ReduceStore {
|
|||
return this.updateElements(state, [action.data]);
|
||||
|
||||
case this.type + '/add-error':
|
||||
// TODO: Add error message
|
||||
return state;
|
||||
|
||||
return state;
|
||||
|
||||
|
||||
case this.type + '/start-remove':
|
||||
this.dataManager.remove(action.data, action.token);
|
||||
|
@ -108,20 +120,41 @@ class ArrayStore extends ReduceStore {
|
|||
});
|
||||
|
||||
case this.type + '/remove-error':
|
||||
// TODO: Add error message
|
||||
if (action.error && !action.error.handled && action.error.response) {
|
||||
|
||||
const USER_REMOVE_ERROR_NOTIFICATION = {
|
||||
title: 'Failed to add remove ',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_REMOVE_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case this.type + '/start-edit':
|
||||
this.dataManager.update(action.data, action.token);
|
||||
return state;
|
||||
|
||||
case this.type + '/start-edit':
|
||||
case this.type + '/start-own-edit':
|
||||
this.dataManager.update(action.data, action.token);
|
||||
return state;
|
||||
|
||||
case this.type + '/edited':
|
||||
return this.updateElements(state, [action.data]);
|
||||
|
||||
case this.type + '/edit-error':
|
||||
// TODO: Add error message
|
||||
case this.type + '/confirm-pw-doesnt-match':
|
||||
const USER_PW_ERROR_NOTIFICATION = {
|
||||
title: 'The new password does not match',
|
||||
message: 'Try again',
|
||||
level: 'error'
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_PW_ERROR_NOTIFICATION);
|
||||
return state;
|
||||
|
||||
case this.type + '/edit-error':
|
||||
return state;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
|
@ -22,7 +22,7 @@
|
|||
import RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
const API_URL = '/api/v1';
|
||||
const API_URL = '/api/v2';
|
||||
|
||||
class RestDataManager {
|
||||
constructor(type, url, keyFilter) {
|
||||
|
@ -114,7 +114,7 @@ class RestDataManager {
|
|||
}
|
||||
|
||||
remove(object, token = null) {
|
||||
RestAPI.delete(this.makeURL(this.url + '/' + object._id), token).then(response => {
|
||||
RestAPI.delete(this.makeURL(this.url + '/' + object.id), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/removed',
|
||||
data: response[this.type],
|
||||
|
@ -132,7 +132,7 @@ class RestDataManager {
|
|||
var obj = {};
|
||||
obj[this.type] = this.filterKeys(object);
|
||||
|
||||
RestAPI.put(this.makeURL(this.url + '/' + object._id), obj, token).then(response => {
|
||||
RestAPI.put(this.makeURL(this.url + '/' + object.id), obj, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edited',
|
||||
data: response[this.type]
|
|
@ -43,7 +43,7 @@ class DeleteDialog extends React.Component {
|
|||
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => this.props.onClose(false)}>Cancel</Button>
|
||||
<Button bsStyle="danger" onClick={() => this.props.onClose(true)}>Delete</Button>
|
||||
<Button variant="danger" onClick={() => this.props.onClose(true)}>Delete</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>;
|
||||
}
|
|
@ -26,14 +26,15 @@ import { NavLink } from 'react-router-dom';
|
|||
export default class HeaderMenu extends React.Component {
|
||||
render() {
|
||||
return <div>
|
||||
<Button className="closeButton" bsStyle="link" onClick={this.props.onClose}>×</Button>
|
||||
|
||||
<Button className="closeButton" variant="link" onClick={this.props.onClose}>×</Button>
|
||||
|
||||
<ul>
|
||||
<li><NavLink to="/home" activeClassName="active" title="Home" onClick={this.props.onClose}>Home</NavLink></li>
|
||||
<li><NavLink to="/projects" activeClassName="active" title="Projects" onClick={this.props.onClose}>Projects</NavLink></li>
|
||||
<li><NavLink to="/simulations" activeClassName="active" title="Simulations" onClick={this.props.onClose}>Simulations</NavLink></li>
|
||||
<li><NavLink to="/simulators" activeClassName="active" title="Simulators" onClick={this.props.onClose}>Simulators</NavLink></li>
|
||||
{ this.props.currentRole === 'admin' ?
|
||||
{ this.props.currentRole === 'Admin' ?
|
||||
<li><NavLink to="/users" activeClassName="active" title="User Management" onClick={this.props.onClose}>User Management</NavLink></li> : ''
|
||||
}
|
||||
<li><NavLink to="/logout" title="Logout" onClick={this.props.onClose}>Logout</NavLink></li>
|
|
@ -21,20 +21,25 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Col, Button } from 'react-bootstrap';
|
||||
import { Hidden } from 'react-grid-system'
|
||||
import Icon from './icon';
|
||||
|
||||
class Header extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<header className="app-header">
|
||||
<Col xs={10} smOffset={2} sm={8}>
|
||||
<Col xs={{span: 10}} sm={{span: 8, offset: 2}}>
|
||||
<h1>VILLASweb</h1>
|
||||
</Col>
|
||||
<Col xs={2} smHidden mdHidden lgHidden style={{ paddingLeft: 'auto', paddingRight: 0 }}>
|
||||
{this.props.showMenuButton &&
|
||||
<Button bsStyle="link" onClick={this.props.onMenuButton} style={{ float: 'right', marginRight: '10px' }}><Icon size="3x" icon="bars" className="menu-icon" /></Button>
|
||||
}
|
||||
</Col>
|
||||
<Hidden sm md lg xl>
|
||||
<Col xs={2} style={{ paddingLeft: 'auto', paddingRight: 0 }}>
|
||||
{this.props.showMenuButton &&
|
||||
<Button variant="link" onClick={this.props.onMenuButton} style={{ float: 'right', marginRight: '10px' }}>
|
||||
<Icon size="3x" icon="bars" className="menu-icon" />
|
||||
</Button>
|
||||
}
|
||||
</Col>
|
||||
</Hidden>
|
||||
</header>
|
||||
);
|
||||
}
|
|
@ -21,16 +21,24 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
//import { Link } from 'react-router-dom';
|
||||
|
||||
import RestAPI from '../api/rest-api';
|
||||
//import RestAPI from '../api/rest-api';
|
||||
|
||||
import config from '../config';
|
||||
import UserStore from "../user/user-store";
|
||||
|
||||
class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
let currentUser = UserStore.getState().currentUser;
|
||||
|
||||
this.state = {
|
||||
currentRole: currentUser ? currentUser.role : '',
|
||||
currentUsername: currentUser ? currentUser.username: '',
|
||||
currentUserID: currentUser ? currentUser.id: 0,
|
||||
token: UserStore.getState().token
|
||||
};
|
||||
}
|
||||
|
||||
getCounts(type) {
|
||||
|
@ -41,9 +49,9 @@ class Home extends React.Component {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
RestAPI.get('/api/v1/counts').then(response => {
|
||||
this.setState({ counts: response });
|
||||
});
|
||||
//RestAPI.get('/api/v1/counts').then(response => {
|
||||
// this.setState({ counts: response });
|
||||
//});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -56,15 +64,21 @@ class Home extends React.Component {
|
|||
VILLASweb is a frontend for distributed real-time simulation hosted by <a href={"mailto:" + config.admin.mail}>{config.admin.name}</a>.
|
||||
</p>
|
||||
<p>
|
||||
This instance is hosting <Link to="/projects" title="Projects">{this.getCounts('projects')} projects</Link> consisting of <Link to="/simulators" title="Simulators">{this.getCounts('simulators')} simulators</Link>, {this.getCounts('visualizations')} visualizations and <Link to="/simulations" title="Simulations">{this.getCounts('simulations')} simulations</Link>.
|
||||
A total of <Link to="/users" title="Users">{this.getCounts('users')} users</Link> are registered.<br />
|
||||
You are logged in as user <b>{this.state.currentUsername}</b> with <b>ID {this.state.currentUserID}</b> and role <b>{this.state.currentRole}</b>.
|
||||
</p>
|
||||
{/*
|
||||
<p>
|
||||
This instance is hosting <Link to="/projects" title="Projects">{this.getCounts('projects')} projects</Link> consisting of <Link to="/simulators" title="Simulators">{this.getCounts('simulators')} simulators</Link>, {this.getCounts('dashboards')} dashboards and <Link to="/simulations" title="Simulations">{this.getCounts('simulations')} simulations</Link>.
|
||||
A total of <Link to="/users" title="Users">{this.getCounts('users')} users</Link> are registered.<br />
|
||||
</p>
|
||||
*/}
|
||||
<h3>Credits</h3>
|
||||
<p>VILLASweb is developed by the <a href="http://acs.eonerc.rwth-aachen.de">Institute for Automation of Complex Power Systems</a> at the <a href="https;//www.rwth-aachen.de">RWTH Aachen University</a>.</p>
|
||||
<ul>
|
||||
<li><a href="mailto:mgrigull@eonerc.rwth-aachen.de">Markus Grigull</a></li>
|
||||
<li><a href="mailto:stvogel@eonerc.rwth-aachen.de">Steffen Vogel</a></li>
|
||||
<li><a href="mailto:mstevic@eonerc.rwth-aachen.de">Marija Stevic</a></li>
|
||||
<li><a href="mailto:sonja.happ@eonerc.rwth-aachen.de">Sonja Happ</a></li>
|
||||
</ul>
|
||||
<h3>Links</h3>
|
||||
<ul>
|
||||
|
@ -75,11 +89,14 @@ class Home extends React.Component {
|
|||
<h3>Funding</h3>
|
||||
<p>The development of <a href="http://fein-aachen.org/projects/villas-framework/">VILLASframework</a> projects have received funding from</p>
|
||||
<ul>
|
||||
<li><a href="http://www.acs.eonerc.rwth-aachen.de/cms/E-ON-ERC-ACS/Forschung/Forschungsprojekte/Gruppe-Real-Time-Simulation-and-Hardware/~qxvw/Urban-Energy-Lab-4/">Urban Energy Lab 4.0</a> a project funded by OP EFRE NRW (European Regional Development Fund) for the setup of a novel energy research infrastructure.</li>
|
||||
<li><a href="http://www.re-serve.eu">RESERVE</a> a European Union’s Horizon 2020 research and innovation programme under grant agreement No 727481</li>
|
||||
<li><a href="http://www.jara.org/en/research/energy">JARA-ENERGY</a>. Jülich-Aachen Research Alliance (JARA) is an initiative of RWTH Aachen University and Forschungszentrum Jülich.</li>
|
||||
</ul>
|
||||
<img height={100} src={require('../img/european_commission.svg')} alt="Logo EU" />
|
||||
<img height={70} src={require('../img/reserve.svg')} alt="Logo EU" />
|
||||
<img height={70} src={require('../img/uel_efre.jpeg')} alt="Logo UEL OP EFRE NRW" />
|
||||
<img height={70} src={require('../img/uel.png')} alt="Logo UEL" />
|
||||
<img height={60} src={require('../img/eonerc_rwth.svg')} alt="Logo ACS" />
|
||||
{
|
||||
//<img height={70} src={require('../img/jara.svg')} alt="Logo JARA" />
|
|
@ -22,6 +22,9 @@
|
|||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
//<li><NavLink to="/simulations" activeClassName="active" title="Simulations">Simulations</NavLink></li>
|
||||
//<li><NavLink to="/projects" activeClassName="active" title="Projects">Projects</NavLink></li>
|
||||
|
||||
class SidebarMenu extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
|
@ -30,12 +33,12 @@ class SidebarMenu extends React.Component {
|
|||
|
||||
<ul>
|
||||
<li><NavLink to="/home" activeClassName="active" title="Home">Home</NavLink></li>
|
||||
<li><NavLink to="/projects" activeClassName="active" title="Projects">Projects</NavLink></li>
|
||||
<li><NavLink to="/simulations" activeClassName="active" title="Simulations">Simulations</NavLink></li>
|
||||
<li><NavLink to="/scenarios" activeClassName="active" title="Scenarios">Scenarios</NavLink></li>
|
||||
<li><NavLink to="/simulators" activeClassName="active" title="Simulators">Simulators</NavLink></li>
|
||||
{ this.props.currentRole === 'admin' ?
|
||||
<li><NavLink to="/users" activeClassName="active" title="User Management">Users</NavLink></li> : ''
|
||||
{ this.props.currentRole === 'Admin' ?
|
||||
<li><NavLink to="/users" activeClassName="active" title="User Management">User Management</NavLink></li> : ''
|
||||
}
|
||||
<li><NavLink to="/user" title="Account">Account</NavLink></li>
|
||||
<li><NavLink to="/logout" title="Logout">Logout</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
|
@ -36,7 +36,10 @@ class TableColumn extends Component {
|
|||
clickable: false,
|
||||
labelKey: null,
|
||||
checkbox: false,
|
||||
checkboxKey: ''
|
||||
checkboxKey: '',
|
||||
labelStyle: null,
|
||||
labelModifier: null
|
||||
|
||||
};
|
||||
|
||||
render() {
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Table, Button, FormControl, Label, Checkbox } from 'react-bootstrap';
|
||||
import { Table, Button, FormControl, FormLabel, FormCheck } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Icon from './icon';
|
||||
|
||||
|
@ -74,7 +74,7 @@ class CustomTable extends Component {
|
|||
if (linkKey && data[linkKey] != null) {
|
||||
cell.push(<Link to={child.props.link + data[linkKey]}>{content}</Link>);
|
||||
} else if (child.props.clickable) {
|
||||
cell.push(<Button bsStyle="link" onClick={() => child.props.onClick(index)}>{content}</Button>);
|
||||
cell.push(<Button variant="link" onClick={() => child.props.onClick(index)}>{content}</Button>);
|
||||
} else {
|
||||
cell.push(content);
|
||||
}
|
||||
|
@ -89,7 +89,13 @@ class CustomTable extends Component {
|
|||
labelContent = child.props.labelModifier(labelContent, data);
|
||||
}
|
||||
|
||||
cell.push(<span> <Label bsClass={child.props.labelStyle(data[labelKey], data)}>{labelContent.toString()}</Label></span>);
|
||||
cell.push(<span>
|
||||
|
||||
<FormLabel column={false} classes={child.props.labelStyle(data[labelKey], data)}>
|
||||
{labelContent.toString()}
|
||||
</FormLabel>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (child.props.dataIndex) {
|
||||
|
@ -98,21 +104,21 @@ class CustomTable extends Component {
|
|||
|
||||
// add buttons
|
||||
if (child.props.editButton) {
|
||||
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onEdit(index)} disabled={child.props.onEdit == null}><Icon icon='edit' /></Button>);
|
||||
cell.push(<Button variant='table-control-button' onClick={() => child.props.onEdit(index)} disabled={child.props.onEdit == null}><Icon icon='edit' /></Button>);
|
||||
}
|
||||
|
||||
if (child.props.deleteButton) {
|
||||
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Icon icon='trash' /></Button>);
|
||||
cell.push(<Button variant='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Icon icon='trash' /></Button>);
|
||||
}
|
||||
|
||||
if (child.props.checkbox) {
|
||||
const checkboxKey = this.props.checkboxKey;
|
||||
|
||||
cell.push(<Checkbox className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
|
||||
cell.push(<FormCheck className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
|
||||
}
|
||||
|
||||
if (child.props.exportButton) {
|
||||
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Icon icon='download' /></Button>);
|
||||
cell.push(<Button variant='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Icon icon='download' /></Button>);
|
||||
}
|
||||
|
||||
return cell;
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* File: edit-user.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 02.05.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
class EditUserDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
mail: '',
|
||||
role: '',
|
||||
_id: ''
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
username: this.props.user.username,
|
||||
mail: this.props.user.mail,
|
||||
role: this.props.user.role,
|
||||
_id: this.props.user._id
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var username = true;
|
||||
|
||||
if (this.state.username === '') {
|
||||
username = false;
|
||||
}
|
||||
|
||||
this.valid = username;
|
||||
|
||||
// return state to control
|
||||
if (target === 'username') return username ? "success" : "error";
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="username" validationState={this.validateForm('username')}>
|
||||
<ControlLabel>Username</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter username" value={this.state.username} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="mail">
|
||||
<ControlLabel>E-mail</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter e-mail" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="role" validationState={this.validateForm('role')}>
|
||||
<ControlLabel>Role</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select role" value={this.state.role} onChange={(e) => this.handleChange(e)}>
|
||||
<option key='1' value='admin'>Administrator</option>
|
||||
<option key='2' value='user'>User</option>
|
||||
<option key='3' value='guest'>Guest</option>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditUserDialog;
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
const config = {
|
||||
publicPathBase: 'public/',
|
||||
instance: 'frontend of the Global RT-SuperLab Demonstration',
|
||||
instance: 'VILLASweb',
|
||||
admin: {
|
||||
name: 'Steffen Vogel',
|
||||
name: 'Institute for Automation of Complex Power Systems, RWTH Aachen University, Germany',
|
||||
mail: 'stvogel@eonerc.rwth-aachen.de'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
/**
|
||||
* File: app.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { Col } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
|
||||
import Home from '../components/home';
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
import SidebarMenu from '../components/menu-sidebar';
|
||||
import HeaderMenu from '../components/header-menu';
|
||||
|
||||
import Projects from './projects';
|
||||
import Project from './project';
|
||||
import Simulators from './simulators';
|
||||
import Visualization from './visualization';
|
||||
import Simulations from './simulations';
|
||||
import Simulation from './simulation';
|
||||
import SimulationModel from './simulation-model';
|
||||
import Users from './users';
|
||||
|
||||
import '../styles/app.css';
|
||||
|
||||
class App extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulatorStore, UserStore, SimulationStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState) {
|
||||
let currentUser = UserStore.getState().currentUser;
|
||||
|
||||
return {
|
||||
simulators: SimulatorStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
currentRole: currentUser ? currentUser.role : '',
|
||||
token: UserStore.getState().token,
|
||||
|
||||
showSidebarMenu: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// if token stored locally, request user
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token != null && token !== '') {
|
||||
// save token so we dont logout
|
||||
this.setState({ token });
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// load all simulators and simulations to fetch simulation data
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.token
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
token: this.state.token
|
||||
});
|
||||
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
}
|
||||
|
||||
showSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: true });
|
||||
}
|
||||
|
||||
hideSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.token == null) {
|
||||
return (<Redirect to="/login" />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} smHidden mdHidden lgHidden className="sidenav">
|
||||
<HeaderMenu onClose={this.hideSidebarMenu} currentRole={this.state.currentRole} />
|
||||
</Col>
|
||||
|
||||
<div className="app">
|
||||
<NotificationSystem ref="notificationSystem" />
|
||||
|
||||
<Header onMenuButton={this.showSidebarMenu} showMenuButton={this.state.token != null} />
|
||||
|
||||
<div className={`app-body app-body-spacing`} >
|
||||
<Col xsHidden>
|
||||
<SidebarMenu currentRole={this.state.currentRole} />
|
||||
</Col>
|
||||
|
||||
<div className={`app-content app-content-margin-left`}>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/home" component={Home} />
|
||||
<Route exact path="/projects" component={Projects} />
|
||||
<Route path="/projects/:project" component={Project} />
|
||||
<Route path="/visualizations/:visualization" component={Visualization} />
|
||||
<Route exact path="/simulations" component={Simulations} />
|
||||
<Route path="/simulations/:simulation" component={Simulation} />
|
||||
<Route path="/simulationModel/:simulationModel" component={SimulationModel} />
|
||||
<Route path="/simulators" component={Simulators} />
|
||||
<Route path="/users" component={Users} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DragDropContext(HTML5Backend)(Container.create(App));
|
|
@ -1,550 +0,0 @@
|
|||
/**
|
||||
* File: visualization.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button, ButtonToolbar } from 'react-bootstrap';
|
||||
import { ContextMenu, Item, Separator } from 'react-contexify';
|
||||
import Fullscreenable from 'react-fullscreenable';
|
||||
import Slider from 'rc-slider';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import WidgetFactory from '../components/widget-factory';
|
||||
import ToolboxItem from '../components/toolbox-item';
|
||||
import Dropzone from '../components/dropzone';
|
||||
import Widget from './widget';
|
||||
import EditWidget from '../components/dialogs/edit-widget';
|
||||
import Grid from '../components/grid';
|
||||
|
||||
import UserStore from '../stores/user-store';
|
||||
import VisualizationStore from '../stores/visualization-store';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import FileStore from '../stores/file-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
import NotificationsFactory from '../data-managers/notifications-factory';
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.min.css';
|
||||
|
||||
class Visualization extends React.Component {
|
||||
static getStores() {
|
||||
return [ VisualizationStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
if (prevState == null) {
|
||||
prevState = {};
|
||||
}
|
||||
|
||||
let simulationModels = [];
|
||||
if (prevState.simulation != null) {
|
||||
simulationModels = SimulationModelStore.getState().filter(m => prevState.simulation.models.includes(m._id));
|
||||
}
|
||||
|
||||
return {
|
||||
sessionToken: UserStore.getState().token,
|
||||
visualizations: VisualizationStore.getState(),
|
||||
projects: ProjectStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
files: FileStore.getState(),
|
||||
|
||||
visualization: prevState.visualization || {},
|
||||
project: prevState.project || null,
|
||||
simulation: prevState.simulation || null,
|
||||
simulationModels,
|
||||
editing: prevState.editing || false,
|
||||
paused: prevState.paused || false,
|
||||
|
||||
editModal: prevState.editModal || false,
|
||||
modalData: prevState.modalData || null,
|
||||
modalIndex: prevState.modalIndex || null,
|
||||
|
||||
maxWidgetHeight: prevState.maxWidgetHeight || 0,
|
||||
dropZoneHeight: prevState.dropZoneHeight || 0
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// TODO: Don't fetch token from local, use user-store!
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
//document.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load',
|
||||
token
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
//document.removeEventListener('keydown', this.handleKeydown.bind(this));
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.visualization._id !== this.props.match.params.visualization) {
|
||||
this.reloadVisualization();
|
||||
}
|
||||
|
||||
// load depending project
|
||||
if (this.state.project == null && this.state.projects) {
|
||||
this.state.projects.forEach((project) => {
|
||||
if (project._id === this.state.visualization.project) {
|
||||
this.setState({ project: project, simulation: null });
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: project.simulation,
|
||||
token
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// load depending simulation
|
||||
if (this.state.simulation == null && this.state.simulations && this.state.project) {
|
||||
this.state.simulations.forEach((simulation) => {
|
||||
if (simulation._id === this.state.project.simulation) {
|
||||
this.setState({ simulation: simulation });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*handleKeydown(e) {
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'p':
|
||||
this.setState({ paused: !this.state.paused });
|
||||
break;
|
||||
case 'e':
|
||||
this.setState({ editing: !this.state.editing });
|
||||
break;
|
||||
case 'f':
|
||||
this.props.toggleFullscreen();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}*/
|
||||
|
||||
transformToWidgetsDict(widgets) {
|
||||
var widgetsDict = {};
|
||||
// Create a new key and make a copy of the widget object
|
||||
var key = 0;
|
||||
widgets.forEach( (widget) => widgetsDict[key++] = Object.assign({}, widget) );
|
||||
return widgetsDict;
|
||||
}
|
||||
|
||||
transformToWidgetsList(widgets) {
|
||||
return Object.keys(widgets).map( (key) => widgets[key]);
|
||||
}
|
||||
|
||||
reloadVisualization() {
|
||||
// select visualization by param id
|
||||
this.state.visualizations.forEach((tempVisualization) => {
|
||||
if (tempVisualization._id === this.props.match.params.visualization) {
|
||||
|
||||
// convert widgets list to a dictionary
|
||||
var visualization = Object.assign({}, tempVisualization, {
|
||||
widgets: tempVisualization.widgets ? this.transformToWidgetsDict(tempVisualization.widgets) : {}
|
||||
});
|
||||
|
||||
this.computeHeightWithWidgets(visualization.widgets);
|
||||
|
||||
this.setState({ visualization: visualization, project: null });
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
data: visualization.project,
|
||||
token
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.state.visualization.grid === 1) return value;
|
||||
|
||||
return Math.round(value / this.state.visualization.grid) * this.state.visualization.grid;
|
||||
}
|
||||
|
||||
handleDrop(item, position) {
|
||||
|
||||
let widget = null;
|
||||
let defaultSimulationModel = null;
|
||||
|
||||
if (this.state.simulation.models && this.state.simulation.models.length === 0) {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.NO_SIM_MODEL_AVAILABLE);
|
||||
} else {
|
||||
defaultSimulationModel = this.state.simulation.models[0];
|
||||
}
|
||||
|
||||
// snap position to grid
|
||||
position.x = this.snapToGrid(position.x);
|
||||
position.y = this.snapToGrid(position.y);
|
||||
|
||||
// create new widget
|
||||
widget = WidgetFactory.createWidgetOfType(item.name, position, defaultSimulationModel);
|
||||
|
||||
var new_widgets = this.state.visualization.widgets;
|
||||
var new_key = Object.keys(new_widgets).length;
|
||||
|
||||
new_widgets[new_key] = widget;
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.increaseHeightWithWidget(widget);
|
||||
this.setState({ visualization: visualization });
|
||||
}
|
||||
|
||||
widgetStatusChange(updated_widget, key) {
|
||||
// Widget changed internally, make changes effective then save them
|
||||
this.widgetChange(updated_widget, key, this.saveChanges);
|
||||
}
|
||||
|
||||
widgetChange(updated_widget, key, callback = null) {
|
||||
var widgets_update = {};
|
||||
widgets_update[key] = updated_widget;
|
||||
var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update);
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
// Check if the height needs to be increased, the section may have shrunk if not
|
||||
if (!this.increaseHeightWithWidget(updated_widget)) {
|
||||
this.computeHeightWithWidgets(visualization.widgets);
|
||||
}
|
||||
this.setState({ visualization: visualization }, callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the initial height state based on the existing widgets
|
||||
*/
|
||||
computeHeightWithWidgets(widgets) {
|
||||
// Compute max height from widgets
|
||||
let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => {
|
||||
let thisWidget = widgets[widgetKey];
|
||||
let thisWidgetHeight = thisWidget.y + thisWidget.height;
|
||||
|
||||
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
|
||||
}, 0);
|
||||
|
||||
this.setState({
|
||||
maxWidgetHeight: maxHeight,
|
||||
dropZoneHeight: maxHeight + 80
|
||||
});
|
||||
}
|
||||
/*
|
||||
* Adapt the area's height with the position of the new widget.
|
||||
* Return true if the height increased, otherwise false.
|
||||
*/
|
||||
increaseHeightWithWidget(widget) {
|
||||
let increased = false;
|
||||
let thisWidgetHeight = widget.y + widget.height;
|
||||
if (thisWidgetHeight > this.state.maxWidgetHeight) {
|
||||
increased = true;
|
||||
this.setState({
|
||||
maxWidgetHeight: thisWidgetHeight,
|
||||
dropZoneHeight: thisWidgetHeight + 40
|
||||
});
|
||||
}
|
||||
return increased;
|
||||
}
|
||||
|
||||
editWidget(e, data) {
|
||||
this.setState({ editModal: true, modalData: this.state.visualization.widgets[data.key], modalIndex: data.key });
|
||||
}
|
||||
|
||||
closeEdit(data) {
|
||||
if (data) {
|
||||
// save changes temporarily
|
||||
var widgets_update = {};
|
||||
widgets_update[this.state.modalIndex] = data;
|
||||
|
||||
var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update);
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.setState({ editModal: false, visualization: visualization });
|
||||
} else {
|
||||
this.setState({ editModal: false });
|
||||
}
|
||||
}
|
||||
|
||||
deleteWidget(e, data) {
|
||||
delete this.state.visualization.widgets[data.key];
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: this.state.visualization.widgets
|
||||
});
|
||||
this.setState({ visualization: visualization });
|
||||
}
|
||||
|
||||
stopEditing() {
|
||||
// Provide the callback so it can be called when state change is applied
|
||||
this.setState({ editing: false }, this.saveChanges );
|
||||
}
|
||||
|
||||
saveChanges() {
|
||||
// Transform to a list
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: this.transformToWidgetsList(this.state.visualization.widgets)
|
||||
});
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-edit',
|
||||
data: visualization,
|
||||
token
|
||||
});
|
||||
}
|
||||
|
||||
discardChanges() {
|
||||
this.setState({ editing: false, visualization: {} });
|
||||
|
||||
this.reloadVisualization();
|
||||
}
|
||||
|
||||
moveWidget(e, data, applyDirection) {
|
||||
var widget = this.state.visualization.widgets[data.key];
|
||||
var updated_widgets = {};
|
||||
updated_widgets[data.key] = applyDirection(widget);
|
||||
var new_widgets = Object.assign({}, this.state.visualization.widgets, updated_widgets);
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.setState({ visualization: visualization });
|
||||
}
|
||||
|
||||
moveAbove(widget) {
|
||||
// increase z-Order
|
||||
widget.z++;
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveToFront(widget) {
|
||||
// increase z-Order
|
||||
widget.z = 100;
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveUnderneath(widget) {
|
||||
// decrease z-Order
|
||||
widget.z--;
|
||||
if (widget.z < 0) {
|
||||
widget.z = 0;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveToBack(widget) {
|
||||
// increase z-Order
|
||||
widget.z = 0;
|
||||
return widget;
|
||||
}
|
||||
|
||||
setGrid(value) {
|
||||
// value 0 would block all widgets, set 1 as 'grid disabled'
|
||||
if (value === 0) {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
let visualization = Object.assign({}, this.state.visualization, {
|
||||
grid: value
|
||||
});
|
||||
|
||||
this.setState({ visualization });
|
||||
}
|
||||
|
||||
lockWidget(data) {
|
||||
// lock the widget
|
||||
let widget = this.state.visualization.widgets[data.key];
|
||||
widget.locked = true;
|
||||
|
||||
// update visualization
|
||||
let widgets = {};
|
||||
widgets[data.key] = widget;
|
||||
widgets = Object.assign({}, this.state.visualization.widgets, widgets);
|
||||
|
||||
const visualization = Object.assign({}, this.state.visualization, { widgets });
|
||||
this.setState({ visualization });
|
||||
}
|
||||
|
||||
unlockWidget(data) {
|
||||
// lock the widget
|
||||
let widget = this.state.visualization.widgets[data.key];
|
||||
widget.locked = false;
|
||||
|
||||
// update visualization
|
||||
let widgets = {};
|
||||
widgets[data.key] = widget;
|
||||
widgets = Object.assign({}, this.state.visualization.widgets, widgets);
|
||||
|
||||
const visualization = Object.assign({}, this.state.visualization, { widgets });
|
||||
this.setState({ visualization });
|
||||
}
|
||||
|
||||
pauseData = () => {
|
||||
this.setState({ paused: true });
|
||||
}
|
||||
|
||||
unpauseData = () => {
|
||||
this.setState({ paused: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const current_widgets = this.state.visualization.widgets;
|
||||
|
||||
let boxClasses = classNames('section', 'box', { 'fullscreen-container': this.props.isFullscreen });
|
||||
|
||||
let buttons = []
|
||||
let editingControls = [];
|
||||
let gridControl = {};
|
||||
|
||||
if (this.state.editing) {
|
||||
buttons.push({ click: () => this.stopEditing(), icon: 'save', text: 'Save' });
|
||||
buttons.push({ click: () => this.discardChanges(), icon: 'ban', text: 'Cancel' });
|
||||
|
||||
gridControl = <div key={editingControls.length}>
|
||||
<span>Grid: {this.state.visualization.grid > 1 ? this.state.visualization.grid : 'Disabled'}</span>
|
||||
<Slider value={this.state.visualization.grid} style={{ width: '80px' }} step={5} onChange={value => this.setGrid(value)} />
|
||||
</div>
|
||||
}
|
||||
|
||||
if (!this.props.isFullscreen) {
|
||||
buttons.push({ click: this.props.toggleFullscreen, icon: 'expand', text: 'Fullscreen' });
|
||||
buttons.push({ click: this.state.paused ? this.unpauseData : this.pauseData, icon: this.state.paused ? 'play' : 'pause', text: this.state.paused ? 'Live' : 'Pause' });
|
||||
|
||||
if (!this.state.editing)
|
||||
buttons.push({ click: () => this.setState({ editing: true }), icon: 'edit', text: 'Edit' });
|
||||
}
|
||||
|
||||
const buttonList = buttons.map((btn, idx) =>
|
||||
<Button key={idx} bsStyle="info" onClick={btn.click} style={{ marginLeft: '8px' }}>
|
||||
<Icon icon={btn.icon} /> {btn.text}
|
||||
</Button>
|
||||
);
|
||||
|
||||
// Only one topology widget at the time is supported
|
||||
let thereIsTopologyWidget = current_widgets && Object.values(current_widgets).filter( widget => widget.type === 'Topology').length > 0;
|
||||
let topologyItemMsg = !thereIsTopologyWidget? '' : 'Currently only one is supported';
|
||||
|
||||
return (
|
||||
<div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<span>{this.state.visualization.name}</span>
|
||||
</div>
|
||||
|
||||
<div className="section-buttons-group-right">
|
||||
{ this.state.editing && gridControl }
|
||||
{ buttonList }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
|
||||
{this.state.editing &&
|
||||
<div className="toolbar">
|
||||
<ButtonToolbar className="section-buttons-group-right">
|
||||
{ editingControls }
|
||||
</ButtonToolbar>
|
||||
<ButtonToolbar className="toolbox box-header">
|
||||
<ToolboxItem icon="star" name="CustomAction" type="widget" />
|
||||
<ToolboxItem icon="play" name="Action" type="widget" disabled={true} />
|
||||
<ToolboxItem icon="lightbulb" name="Lamp" type="widget" />
|
||||
<ToolboxItem icon="font" name="Value" type="widget" />
|
||||
<ToolboxItem icon="chart-area" name="Plot" type="widget" />
|
||||
<ToolboxItem icon="table" name="Table" type="widget" />
|
||||
<ToolboxItem icon="tag" name="Label" type="widget" />
|
||||
<ToolboxItem icon="image" name="Image" type="widget" />
|
||||
<ToolboxItem icon="table" name="PlotTable" type="widget" />
|
||||
<ToolboxItem icon="dot-circle" name="Button" type="widget" />
|
||||
<ToolboxItem icon="i-cursor" name="Input" type="widget" />
|
||||
<ToolboxItem icon="sliders-h" name="Slider" type="widget" />
|
||||
<ToolboxItem icon="tachometer-alt" name="Gauge" type="widget" />
|
||||
<ToolboxItem icon="square" name="Box" type="widget" />
|
||||
<ToolboxItem icon="code" name="HTML" type="html" />
|
||||
<ToolboxItem icon="project-diagram" name="Topology" type="widget" disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Dropzone height={this.state.dropZoneHeight} onDrop={(item, position) => this.handleDrop(item, position)} editing={this.state.editing}>
|
||||
{current_widgets != null &&
|
||||
Object.keys(current_widgets).map(widget_key => (
|
||||
<Widget
|
||||
key={widget_key}
|
||||
data={current_widgets[widget_key]}
|
||||
simulation={this.state.simulation}
|
||||
onWidgetChange={(w, k) => this.widgetChange(w, k)}
|
||||
onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)}
|
||||
editing={this.state.editing}
|
||||
index={widget_key}
|
||||
grid={this.state.visualization.grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Grid size={this.state.visualization.grid} disabled={this.state.visualization.grid === 1 || !this.state.editing} />
|
||||
</Dropzone>
|
||||
|
||||
{current_widgets != null &&
|
||||
Object.keys(current_widgets).map(widget_key => {
|
||||
const data = { key: widget_key };
|
||||
|
||||
const locked = this.state.visualization.widgets[widget_key].locked;
|
||||
const disabledMove = locked || this.state.visualization.widgets[widget_key].type === 'Box';
|
||||
|
||||
return <ContextMenu style={{zIndex: 100}} id={'widgetMenu'+ widget_key} key={widget_key}>
|
||||
<Item disabled={locked} onClick={e => this.editWidget(e, data)}>Edit</Item>
|
||||
<Item disabled={locked} onClick={e => this.deleteWidget(e, data)}>Delete</Item>
|
||||
<Separator />
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveAbove)}>Move above</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveToFront)}>Move to front</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveUnderneath)}>Move underneath</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveToBack)}>Move to back</Item>
|
||||
<Separator />
|
||||
<Item disabled={locked} onClick={e => this.lockWidget(data)}>Lock</Item>
|
||||
<Item disabled={!locked} onClick={e => this.unlockWidget(data)}>Unlock</Item>
|
||||
</ContextMenu>
|
||||
})}
|
||||
|
||||
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={(data) => this.closeEdit(data)} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Fullscreenable()(Container.create(Visualization, { withProps: true }));
|
|
@ -1,297 +0,0 @@
|
|||
/**
|
||||
* File: widget.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { ContextMenuProvider } from 'react-contexify';
|
||||
import Rnd from 'react-rnd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/user-store';
|
||||
import SimulatorDataStore from '../stores/simulator-data-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import FileStore from '../stores/file-store';
|
||||
|
||||
import WidgetCustomAction from '../components/widgets/custom-action';
|
||||
import WidgetAction from '../components/widgets/action';
|
||||
import WidgetLamp from '../components/widgets/lamp';
|
||||
import WidgetValue from '../components/widgets/value';
|
||||
import WidgetPlot from '../components/widgets/plot';
|
||||
import WidgetTable from '../components/widgets/table';
|
||||
import WidgetLabel from '../components/widgets/label';
|
||||
import WidgetPlotTable from '../components/widgets/plot-table';
|
||||
import WidgetImage from '../components/widgets/image';
|
||||
import WidgetButton from '../components/widgets/button';
|
||||
import WidgetInput from '../components/widgets/input';
|
||||
import WidgetSlider from '../components/widgets/slider';
|
||||
import WidgetGauge from '../components/widgets/gauge';
|
||||
import WidgetBox from '../components/widgets/box';
|
||||
import WidgetHTML from '../components/widgets/html';
|
||||
import WidgetTopology from '../components/widgets/topology';
|
||||
|
||||
import '../styles/widgets.css';
|
||||
|
||||
class Widget extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulatorDataStore, SimulationModelStore, FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
let simulatorData = {};
|
||||
|
||||
if (props.paused) {
|
||||
if (prevState && prevState.simulatorData) {
|
||||
simulatorData = JSON.parse(JSON.stringify(prevState.simulatorData));
|
||||
}
|
||||
} else {
|
||||
simulatorData = SimulatorDataStore.getState();
|
||||
}
|
||||
|
||||
if (prevState) {
|
||||
return {
|
||||
sessionToken,
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
sequence: prevState.sequence + 1,
|
||||
|
||||
simulationModels: SimulationModelStore.getState()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sessionToken,
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
sequence: 0,
|
||||
|
||||
simulationModels: SimulationModelStore.getState()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Reference to the context menu element
|
||||
this.contextMenuTriggerViaDraggable = null;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// If loading for the first time
|
||||
if (this.state.sessionToken) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.props.grid === 1)
|
||||
return value;
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
drag(event, data) {
|
||||
const x = this.snapToGrid(data.x);
|
||||
const y = this.snapToGrid(data.y);
|
||||
|
||||
if (x !== data.x || y !== data.y) {
|
||||
this.rnd.updatePosition({ x, y });
|
||||
}
|
||||
}
|
||||
|
||||
dragStop(event, data) {
|
||||
// update widget
|
||||
let widget = this.props.data;
|
||||
widget.x = this.snapToGrid(data.x);
|
||||
widget.y = this.snapToGrid(data.y);
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
resizeStop(direction, delta, event) {
|
||||
// update widget
|
||||
let widget = Object.assign({}, this.props.data);
|
||||
|
||||
// resize depends on direction
|
||||
if (direction === 'left' || direction === 'topLeft' || direction === 'bottomLeft') {
|
||||
widget.x -= delta.width;
|
||||
}
|
||||
|
||||
if (direction === 'top' || direction === 'topLeft' || direction === 'topRight') {
|
||||
widget.y -= delta.height;
|
||||
}
|
||||
|
||||
widget.width += delta.width;
|
||||
widget.height += delta.height;
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
borderWasClicked(e) {
|
||||
// check if it was triggered by the right button
|
||||
if (e.button === 2) {
|
||||
// launch the context menu using the reference
|
||||
if(this.contextMenuTriggerViaDraggable) {
|
||||
this.contextMenuTriggerViaDraggable.handleContextClick(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputDataChanged(widget, data) {
|
||||
let simulationModel = null;
|
||||
|
||||
for (let model of this.state.simulationModels) {
|
||||
if (model._id !== widget.simulationModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
simulationModel = model;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/inputChanged',
|
||||
simulator: simulationModel.simulator,
|
||||
signal: widget.signal,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// configure grid
|
||||
const grid = [this.props.grid, this.props.grid];
|
||||
|
||||
// get widget element
|
||||
const widget = this.props.data;
|
||||
let borderedWidget = false;
|
||||
let element = null;
|
||||
let zIndex = Number(widget.z);
|
||||
|
||||
let simulationModel = null;
|
||||
|
||||
for (let model of this.state.simulationModels) {
|
||||
if (model._id !== widget.simulationModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
simulationModel = model;
|
||||
}
|
||||
|
||||
// dummy is passed to widgets to keep updating them while in edit mode
|
||||
if (widget.type === 'CustomAction') {
|
||||
element = <WidgetCustomAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Action') {
|
||||
element = <WidgetAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Lamp') {
|
||||
element = <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Value') {
|
||||
element = <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Plot') {
|
||||
element = <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} paused={this.props.paused} />
|
||||
} else if (widget.type === 'Table') {
|
||||
element = <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Label') {
|
||||
element = <WidgetLabel widget={widget} />
|
||||
} else if (widget.type === 'PlotTable') {
|
||||
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
|
||||
} else if (widget.type === 'Image') {
|
||||
element = <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
|
||||
} else if (widget.type === 'Button') {
|
||||
element = <WidgetButton widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Input') {
|
||||
element = <WidgetInput widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Slider') {
|
||||
element = <WidgetSlider widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } />
|
||||
} else if (widget.type === 'Gauge') {
|
||||
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Box') {
|
||||
element = <WidgetBox widget={widget} editing={this.props.editing} />
|
||||
} else if (widget.type === 'HTML') {
|
||||
element = <WidgetHTML widget={widget} editing={this.props.editing} />
|
||||
} else if (widget.type === 'Topology') {
|
||||
element = <WidgetTopology widget={widget} files={this.state.files} />
|
||||
}
|
||||
|
||||
if (widget.type === 'Box')
|
||||
zIndex = 0;
|
||||
|
||||
const widgetClasses = classNames({
|
||||
'widget': !this.props.editing,
|
||||
'editing-widget': this.props.editing,
|
||||
'border': borderedWidget,
|
||||
'unselectable': false,
|
||||
'locked': widget.locked && this.props.editing
|
||||
});
|
||||
|
||||
if (this.props.editing) {
|
||||
const resizing = { bottom: !widget.locked, bottomLeft: !widget.locked, bottomRight: !widget.locked, left: !widget.locked, right: !widget.locked, top: !widget.locked, topLeft: !widget.locked, topRight: !widget.locked};
|
||||
|
||||
return (
|
||||
<Rnd
|
||||
ref={c => { this.rnd = c; }}
|
||||
default={{ x: Number(widget.x), y: Number(widget.y), width: widget.width, height: widget.height }}
|
||||
minWidth={widget.minWidth}
|
||||
minHeight={widget.minHeight}
|
||||
lockAspectRatio={Boolean(widget.lockAspect)}
|
||||
bounds={'parent'}
|
||||
className={ widgetClasses }
|
||||
onResizeStart={(event, direction, ref) => this.borderWasClicked(event)}
|
||||
onResizeStop={(event, direction, ref, delta) => this.resizeStop(direction, delta, event)}
|
||||
onDrag={(event, data) => this.drag(event, data)}
|
||||
onDragStop={(event, data) => this.dragStop(event, data)}
|
||||
dragGrid={grid}
|
||||
resizeGrid={grid}
|
||||
z={zIndex}
|
||||
enableResizing={resizing}
|
||||
disableDragging={widget.locked}
|
||||
>
|
||||
<ContextMenuProvider className={'full'} id={'widgetMenu' + this.props.index}>
|
||||
{element}
|
||||
</ContextMenuProvider>
|
||||
</Rnd>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={ widgetClasses }
|
||||
style={{
|
||||
width: Number(widget.width), height: Number(widget.height),
|
||||
left: Number(widget.x), top: Number(widget.y),
|
||||
zIndex: zIndex,
|
||||
position: 'absolute'
|
||||
}}>
|
||||
{element}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Widget, { withProps: true });
|
96
src/dashboard/dashboard-button-group.js
Normal file
96
src/dashboard/dashboard-button-group.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* File: dashboard-button-group.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
class DashboardButtonGroup extends React.Component {
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '8px'
|
||||
};
|
||||
|
||||
const buttons = [];
|
||||
let key = 0;
|
||||
|
||||
if (this.props.fullscreen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.editing) {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onSave} style={buttonStyle}>
|
||||
<span class="glyphicon glyphicon-floppy-disk"></span> Save
|
||||
</Button>,
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onCancel} style={buttonStyle}>
|
||||
<span class="glyphicon glyphicon-remove" ></span> Cancel
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
if (this.props.fullscreen !== true) {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-resize-full"></span> Fullscreen
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.paused) {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onUnpause} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-play"></span> Live
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onPause} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-pause"></span> Pause
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<Button key={key++} bsStyle="info" onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<span className="glyphicon glyphicon-pencil"></span> Pause
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className='section-buttons-group-right'>
|
||||
{buttons}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
DashboardButtonGroup.propTypes = {
|
||||
editing: PropTypes.bool,
|
||||
fullscreen: PropTypes.bool,
|
||||
paused: PropTypes.bool,
|
||||
onEdit: PropTypes.func,
|
||||
onSave: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onFullscreen: PropTypes.func,
|
||||
onPause: PropTypes.func,
|
||||
onUnpause: PropTypes.func
|
||||
};
|
||||
|
||||
export default DashboardButtonGroup;
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* File: visualization-store.js
|
||||
* File: dashboard-store.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
|
@ -19,7 +19,7 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import VisualizationsDataManager from '../data-managers/visualizations-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import DashboardsDataManager from './dashboards-data-manager';
|
||||
|
||||
export default new ArrayStore('visualizations', VisualizationsDataManager);
|
||||
export default new ArrayStore('dashboards', DashboardsDataManager);
|
423
src/dashboard/dashboard.js
Normal file
423
src/dashboard/dashboard.js
Normal file
|
@ -0,0 +1,423 @@
|
|||
/**
|
||||
* File: dashboard.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import Fullscreenable from 'react-fullscreenable';
|
||||
import classNames from 'classnames';
|
||||
import { Map } from 'immutable'
|
||||
|
||||
//import Icon from '../common/icon';
|
||||
import Widget from '../widget/widget';
|
||||
import EditWidget from '../widget/edit-widget';
|
||||
|
||||
import WidgetContextMenu from './widget-context-menu';
|
||||
import WidgetToolbox from './widget-toolbox';
|
||||
import WidgetArea from './widget-area';
|
||||
import DashboardButtonGroup from './dashboard-button-group';
|
||||
|
||||
import UserStore from '../user/user-store';
|
||||
import DashboardStore from './dashboard-store';
|
||||
import ProjectStore from '../project/project-store';
|
||||
import SimulationStore from '../simulation/simulation-store';
|
||||
import SimulationModelStore from '../simulationmodel/simulation-model-store';
|
||||
import FileStore from '../file/file-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.min.css';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
|
||||
static lastWidgetKey = 0;
|
||||
static getStores() {
|
||||
return [ DashboardStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
if (prevState == null) {
|
||||
prevState = {};
|
||||
}
|
||||
|
||||
let dashboard = Map();
|
||||
let rawDashboard = DashboardStore.getState().find(v => v._id === props.match.params.dashboard);
|
||||
|
||||
if (rawDashboard != null) {
|
||||
dashboard = Map(rawDashboard);
|
||||
|
||||
// convert widgets list to a dictionary to be able to reference widgets
|
||||
const widgets = {};
|
||||
|
||||
for (let widget of dashboard.get('widgets')) {
|
||||
widgets[this.getNewWidgetKey()] = widget;
|
||||
}
|
||||
|
||||
dashboard = dashboard.set('widgets', widgets);
|
||||
|
||||
// this.computeHeightWithWidgets(widgets);
|
||||
|
||||
// this.setState({ dashboard: selectedDashboards, project: null });
|
||||
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'projects/start-load',
|
||||
// data: selectedDashboard.get('project'),
|
||||
// token: this.state.sessionToken
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
let simulationModels = [];
|
||||
if (prevState.simulation != null) {
|
||||
simulationModels = SimulationModelStore.getState().filter(m => prevState.simulation.models.includes(m._id));
|
||||
}
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
|
||||
sessionToken: UserStore.getState().token,
|
||||
projects: ProjectStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
files: FileStore.getState(),
|
||||
|
||||
project: prevState.project || null,
|
||||
simulation: prevState.simulation || null,
|
||||
simulationModels,
|
||||
editing: prevState.editing || false,
|
||||
paused: prevState.paused || false,
|
||||
|
||||
editModal: prevState.editModal || false,
|
||||
modalData: prevState.modalData || null,
|
||||
modalIndex: prevState.modalIndex || null,
|
||||
|
||||
maxWidgetHeight: prevState.maxWidgetHeight || 0,
|
||||
dropZoneHeight: prevState.dropZoneHeight || 0,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static getNewWidgetKey() {
|
||||
const widgetKey = this.lastWidgetKey;
|
||||
this.lastWidgetKey++;
|
||||
|
||||
return widgetKey;
|
||||
}
|
||||
|
||||
|
||||
componentWillMount() {
|
||||
//document.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||
|
||||
if (this.state.dashboard.has('id') === false) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-load',
|
||||
data: this.props.match.params.dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
//document.removeEventListener('keydown', this.handleKeydown.bind(this));
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.dashboard._id !== this.props.match.params.dashboard) {
|
||||
this.reloadDashboard();
|
||||
}
|
||||
|
||||
// load depending project
|
||||
if (this.state.project == null && this.state.projects) {
|
||||
this.state.projects.forEach((project) => {
|
||||
if (project._id === this.state.dashboard.project) {
|
||||
this.setState({ project: project, simulation: null });
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: project.simulation,
|
||||
token
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// load depending simulation
|
||||
if (this.state.simulation == null && this.state.simulations && this.state.project) {
|
||||
this.state.simulations.forEach((simulation) => {
|
||||
if (simulation._id === this.state.project.simulation) {
|
||||
this.setState({ simulation: simulation });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*handleKeydown(e) {
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'p':
|
||||
this.setState({ paused: !this.state.paused });
|
||||
break;
|
||||
case 'e':
|
||||
this.setState({ editing: !this.state.editing });
|
||||
break;
|
||||
case 'f':
|
||||
this.props.toggleFullscreen();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}*/
|
||||
|
||||
/*
|
||||
* Adapt the area's height with the position of the new widget.
|
||||
* Return true if the height increased, otherwise false.
|
||||
*/
|
||||
increaseHeightWithWidget(widget) {
|
||||
let increased = false;
|
||||
let thisWidgetHeight = widget.y + widget.height;
|
||||
|
||||
if (thisWidgetHeight > this.state.maxWidgetHeight) {
|
||||
increased = true;
|
||||
|
||||
this.setState({
|
||||
maxWidgetHeight: thisWidgetHeight,
|
||||
dropZoneHeight: thisWidgetHeight + 40
|
||||
});
|
||||
}
|
||||
|
||||
return increased;
|
||||
}
|
||||
|
||||
transformToWidgetsList(widgets) {
|
||||
return Object.keys(widgets).map( (key) => widgets[key]);
|
||||
}
|
||||
|
||||
reloadDashboard() {
|
||||
// select dashboard by param id
|
||||
this.state.dashboards.forEach((tempDashboard) => {
|
||||
if (tempDashboard._id === this.props.match.params.dashboard) {
|
||||
|
||||
// convert widgets list to a dictionary
|
||||
var dashboard = Object.assign({}, tempDashboard, {
|
||||
widgets: tempDashboard.widgets ? this.transformToWidgetsDict(tempDashboard.widgets) : {}
|
||||
});
|
||||
|
||||
this.computeHeightWithWidgets(dashboard.widgets);
|
||||
|
||||
this.setState({ dashboard: dashboard, project: null });
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
data: dashboard.project,
|
||||
token
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleDrop = widget => {
|
||||
const widgets = this.state.dashboard.get('widgets') || [];
|
||||
|
||||
const widgetKey = this.getNewWidgetKey();
|
||||
widgets[widgetKey] = widget;
|
||||
|
||||
const dashboard = this.state.dashboard.set('widgets');
|
||||
|
||||
// this.increaseHeightWithWidget(widget);
|
||||
|
||||
this.setState({ dashboard });
|
||||
};
|
||||
|
||||
|
||||
widgetStatusChange(updated_widget, key) {
|
||||
// Widget changed internally, make changes effective then save them
|
||||
this.widgetChange(updated_widget, key, this.saveChanges);
|
||||
}
|
||||
|
||||
widgetChange = (widget, index, callback = null) => {
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
widgets[index] = widget;
|
||||
|
||||
const dashboard = this.state.dashboard.set('widgets');
|
||||
|
||||
// Check if the height needs to be increased, the section may have shrunk if not
|
||||
if (!this.increaseHeightWithWidget(widget)) {
|
||||
this.computeHeightWithWidgets(dashboard.widgets);
|
||||
}
|
||||
|
||||
this.setState({ dashboard }, callback);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set the initial height state based on the existing widgets
|
||||
*/
|
||||
computeHeightWithWidgets(widgets) {
|
||||
// Compute max height from widgets
|
||||
let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => {
|
||||
let thisWidget = widgets[widgetKey];
|
||||
let thisWidgetHeight = thisWidget.y + thisWidget.height;
|
||||
|
||||
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
|
||||
}, 0);
|
||||
|
||||
this.setState({
|
||||
maxWidgetHeight: maxHeight,
|
||||
dropZoneHeight: maxHeight + 80
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
editWidget = (widget, index) => {
|
||||
this.setState({ editModal: true, modalData: widget, modalIndex: index });
|
||||
}
|
||||
|
||||
|
||||
closeEdit = data => {
|
||||
if (data == null) {
|
||||
this.setState({ editModal: false });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
widgets[this.state.modalIndex] = data;
|
||||
|
||||
const dashboard = this.state.dashboard.set('widgets', widgets);
|
||||
|
||||
this.setState({ editModal: false, dashboard });
|
||||
};
|
||||
|
||||
|
||||
deleteWidget = (widget, index) => {
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
delete widgets[index];
|
||||
|
||||
const dashboard = this.state.dashboard.set('widgets');
|
||||
|
||||
this.setState({ dashboard });
|
||||
};
|
||||
|
||||
|
||||
startEditing = () => {
|
||||
this.setState({ editing: true });
|
||||
};
|
||||
|
||||
saveEditing = () => {
|
||||
// Provide the callback so it can be called when state change is applied
|
||||
// TODO: Check if callback is needed
|
||||
this.setState({ editing: false }, this.saveChanges );
|
||||
};
|
||||
|
||||
saveChanges() {
|
||||
// Transform to a list
|
||||
const dashboard = Object.assign({}, this.state.dashboard.toJS(), {
|
||||
widgets: this.transformToWidgetsList(this.state.dashboard.get('widgets'))
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
cancelEditing = () => {
|
||||
this.setState({ editing: false, dasboard: {} });
|
||||
|
||||
this.reloadDashboard();
|
||||
};
|
||||
|
||||
setGrid = value => {
|
||||
const dashboard = this.state.dashboard.set('grid', value);
|
||||
|
||||
this.setState({ dashboard });
|
||||
};
|
||||
|
||||
pauseData = () => {
|
||||
this.setState({ paused: true });
|
||||
};
|
||||
|
||||
unpauseData = () => {
|
||||
this.setState({ paused: false });
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
const widgets = this.state.dashboard.get('widgets');
|
||||
const grid = this.state.dashboard.get('grid');
|
||||
|
||||
const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen });
|
||||
|
||||
return <div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<span>{this.state.dashboard.get('name')}</span>
|
||||
</div>
|
||||
|
||||
<DashboardButtonGroup
|
||||
editing={this.state.editing}
|
||||
fullscreen={this.props.isFullscreen}
|
||||
paused={this.state.paused}
|
||||
onEdit={this.startEditing}
|
||||
onSave={this.saveEditing}
|
||||
onCancel={this.cancelEditing}
|
||||
onFullscreen={this.props.toggleFullscreen}
|
||||
onPause={this.pauseData}
|
||||
onUnpause={this.unpauseData}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
|
||||
{this.state.editing &&
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid} widgets={widgets} />
|
||||
}
|
||||
|
||||
<WidgetArea widgets={widgets} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop}>
|
||||
{widgets != null && Object.keys(widgets).map(widgetKey => (
|
||||
<Widget
|
||||
key={widgetKey}
|
||||
data={widgets[widgetKey]}
|
||||
simulation={this.state.simulation}
|
||||
onWidgetChange={(w, k) => this.widgetChange(w, k)}
|
||||
onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)}
|
||||
editing={this.state.editing}
|
||||
index={widgetKey}
|
||||
grid={grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
))}
|
||||
</WidgetArea>
|
||||
|
||||
{/* TODO: Create only one context menu for all widgets */}
|
||||
{widgets != null && Object.keys(widgets).map(widgetKey => (
|
||||
<WidgetContextMenu key={widgetKey} index={widgetKey} widget={widgets[widgetKey]} onEdit={this.editWidget} onDelete={this.deleteWidget} onChange={this.widgetChange} />
|
||||
))}
|
||||
|
||||
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={this.closeEdit} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Fullscreenable()(Container.create(fluxContainerConverter.convert(Dashboard), { withProps: true }));
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* File: visualizations-data-manager.js
|
||||
* File: dashboards-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
|
@ -19,6 +19,6 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
|
||||
export default new RestDataManager('visualization', '/visualizations');
|
||||
export default new RestDataManager('dashboard', '/dashboards');
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* File: new-visualization.js
|
||||
* File: new-dashboard.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
|
@ -20,11 +20,11 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditVisualizationDialog extends React.Component {
|
||||
class EditDashboardDialog extends React.Component {
|
||||
valid: false;
|
||||
|
||||
constructor(props) {
|
||||
|
@ -52,8 +52,8 @@ class EditVisualizationDialog extends React.Component {
|
|||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: this.props.visualization.name,
|
||||
_id: this.props.visualization._id
|
||||
name: this.props.dashboard.name,
|
||||
_id: this.props.dashboard._id
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -75,10 +75,10 @@ class EditVisualizationDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Visualization" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="Edit Dashboard" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
@ -88,4 +88,4 @@ class EditVisualizationDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default EditVisualizationDialog;
|
||||
export default EditDashboardDialog;
|
|
@ -20,11 +20,11 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class ImportVisualizationDialog extends React.Component {
|
||||
class ImportDashboardDialog extends React.Component {
|
||||
valid = false;
|
||||
imported = false;
|
||||
|
||||
|
@ -69,14 +69,14 @@ class ImportVisualizationDialog extends React.Component {
|
|||
|
||||
reader.onload = function(event) {
|
||||
// read simulator
|
||||
const visualization = JSON.parse(event.target.result);
|
||||
const dashboard = JSON.parse(event.target.result);
|
||||
|
||||
let defaultSimulator = "";
|
||||
if (self.props.simulation.models != null) {
|
||||
defaultSimulator = self.props.simulation.models[0].simulator;
|
||||
}
|
||||
|
||||
visualization.widgets.forEach(widget => {
|
||||
dashboard.widgets.forEach(widget => {
|
||||
switch (widget.type) {
|
||||
case 'Value':
|
||||
case 'Plot':
|
||||
|
@ -93,7 +93,7 @@ class ImportVisualizationDialog extends React.Component {
|
|||
|
||||
self.imported = true;
|
||||
self.valid = true;
|
||||
self.setState({ name: visualization.name, widgets: visualization.widgets, grid: visualization.grid });
|
||||
self.setState({ name: dashboard.name, widgets: dashboard.widgets, grid: dashboard.grid });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
|
@ -115,15 +115,15 @@ class ImportVisualizationDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Import Visualization" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="Import Dashboard" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="file">
|
||||
<ControlLabel>Visualization File</ControlLabel>
|
||||
<FormLabel>Dashboard File</FormLabel>
|
||||
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
@ -133,4 +133,4 @@ class ImportVisualizationDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default ImportVisualizationDialog;
|
||||
export default ImportDashboardDialog;
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* File: new-visualization.js
|
||||
* File: new-dashboard.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
|
@ -20,9 +20,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class NewVisualzationDialog extends React.Component {
|
||||
valid: false;
|
||||
|
@ -71,10 +71,10 @@ class NewVisualzationDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New Visualization" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="New Dashboard" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
|
@ -22,7 +22,7 @@
|
|||
import React from 'react';
|
||||
import { DragSource } from 'react-dnd';
|
||||
import classNames from 'classnames';
|
||||
import Icon from './icon';
|
||||
import Icon from '../common/icon';
|
||||
|
||||
const toolboxItemSource = {
|
||||
beginDrag(props) {
|
78
src/dashboard/widget-area.js
Normal file
78
src/dashboard/widget-area.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* File: widget-area.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Dropzone from './dropzone';
|
||||
import Grid from './grid';
|
||||
|
||||
import WidgetFactory from '../widget/widget-factory';
|
||||
|
||||
class WidgetArea extends React.Component {
|
||||
snapToGrid(value) {
|
||||
if (this.props.grid === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
handleDrop = (item, position) => {
|
||||
position.x = this.snapToGrid(position.x);
|
||||
position.y = this.snapToGrid(position.y);
|
||||
|
||||
const widget = WidgetFactory.createWidgetOfType(item.name, position, this.props.defaultSimulationModel);
|
||||
|
||||
if (this.props.onWidgetAdded != null) {
|
||||
this.props.onWidgetAdded(widget);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const maxHeight = Object.values(this.props.widgets).reduce((currentHeight, widget) => {
|
||||
const absolutHeight = widget.y + widget.height;
|
||||
|
||||
return absolutHeight > currentHeight ? absolutHeight : currentHeight;
|
||||
}, 0);
|
||||
|
||||
return <Dropzone height={maxHeight + 80} onDrop={this.handleDrop} editing={this.props.editing}>
|
||||
{this.props.children}
|
||||
|
||||
<Grid size={this.props.grid} disabled={this.props.grid === 1 || this.props.editing !== true} />
|
||||
</Dropzone>;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetArea.propTypes = {
|
||||
children: PropTypes.node, //TODO is .node correct here? Was .children before leading to compile error
|
||||
editing: PropTypes.bool,
|
||||
grid: PropTypes.number,
|
||||
defaultSimulationModel: PropTypes.string,
|
||||
widgets: PropTypes.object,
|
||||
onWidgetAdded: PropTypes.func
|
||||
};
|
||||
|
||||
WidgetArea.defaultProps = {
|
||||
widgets: {}
|
||||
};
|
||||
|
||||
export default WidgetArea;
|
123
src/dashboard/widget-context-menu.js
Normal file
123
src/dashboard/widget-context-menu.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* File: widget-context-menu.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { contextMenu, Item, Separator } from 'react-contexify';
|
||||
|
||||
class WidgetContextMenu extends React.Component {
|
||||
editWidget = event => {
|
||||
if (this.props.onEdit != null) {
|
||||
this.props.onEdit(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
deleteWidget = event => {
|
||||
if (this.props.onDelete != null) {
|
||||
this.props.onDelete(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveAbove = event => {
|
||||
this.props.widget.z++;
|
||||
if (this.props.widget.z > 100) {
|
||||
this.props.widget.z = 100;
|
||||
}
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveToFront = event => {
|
||||
this.props.widget.z = 100;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveUnderneath = event => {
|
||||
this.props.widget.z--;
|
||||
if (this.props.widget.z < 0) {
|
||||
this.props.widget.z = 0;
|
||||
}
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
moveToBack = event => {
|
||||
this.props.widget.z = 0;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
lockWidget = event => {
|
||||
this.props.widget.locked = true;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
unlockWidget = event => {
|
||||
this.props.widget.locked = false;
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.props.widget, this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const isLocked = this.props.widget.locked;
|
||||
|
||||
return <contextMenu id={'widgetMenu'+ this.props.index}>
|
||||
<Item disabled={isLocked} onClick={this.editWidget}>Edit</Item>
|
||||
<Item disabled={isLocked} onClick={this.deleteWidget}>Delete</Item>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Item disabled={isLocked} onClick={this.moveAbove}>Move above</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToFront}>Move to front</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveUnderneath}>Move underneath</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToBack}>Move to back</Item>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Item disabled={isLocked} onClick={this.lockWidget}>Lock</Item>
|
||||
<Item disabled={isLocked === false} onClick={this.unlockWidget}>Unlock</Item>
|
||||
</contextMenu>;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetContextMenu.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
widget: PropTypes.object.isRequired,
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default WidgetContextMenu
|
77
src/dashboard/widget-toolbox.js
Normal file
77
src/dashboard/widget-toolbox.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* File: widget-toolbox.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 31.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Slider from 'rc-slider';
|
||||
|
||||
import ToolboxItem from './toolbox-item';
|
||||
|
||||
class WidgetToolbox extends React.Component {
|
||||
onGridChange = value => {
|
||||
// value 0 would block all widgets, set 1 as 'grid disabled'
|
||||
if (value === 0) {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
if (this.props.onGridChange != null) {
|
||||
this.props.onGridChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
// Only one topology widget at the time is supported
|
||||
const thereIsTopologyWidget = this.props.widgets != null && Object.values(this.props.widgets).filter(w => w.type === 'Topology').length > 0;
|
||||
const topologyItemMsg = thereIsTopologyWidget? 'Currently only one is supported' : '';
|
||||
|
||||
return <div className='toolbox box-header'>
|
||||
<ToolboxItem name='Lamp' type='widget' />
|
||||
<ToolboxItem name='Value' type='widget' />
|
||||
<ToolboxItem name='Plot' type='widget' />
|
||||
<ToolboxItem name='Table' type='widget' />
|
||||
<ToolboxItem name='Label' type='widget' />
|
||||
<ToolboxItem name='Image' type='widget' />
|
||||
<ToolboxItem name='PlotTable' type='widget' />
|
||||
<ToolboxItem name='Button' type='widget' />
|
||||
<ToolboxItem name='NumberInput' type='widget' />
|
||||
<ToolboxItem name='Slider' type='widget' />
|
||||
<ToolboxItem name='Gauge' type='widget' />
|
||||
<ToolboxItem name='Box' type='widget' />
|
||||
<ToolboxItem name='HTML' type='html' />
|
||||
<ToolboxItem name='Topology' type='widget' disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
|
||||
|
||||
<div className='section-buttons-group-right'>
|
||||
<div>
|
||||
<span>Grid: { this.props.grid > 1 ? this.props.grid : 'Disabled' }</span>
|
||||
<Slider value={this.props.grid} style={{ width: '80px' }} step={5} onChange={this.onGridChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
}
|
||||
|
||||
WidgetToolbox.propTypes = {
|
||||
widgets: PropTypes.array,
|
||||
grid: PropTypes.number,
|
||||
onGridChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default WidgetToolbox;
|
|
@ -19,8 +19,8 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import FilesDataManager from '../data-managers/files-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import FilesDataManager from './files-data-manager';
|
||||
|
||||
class FileStore extends ArrayStore {
|
||||
constructor() {
|
|
@ -19,9 +19,9 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import RestAPI from '../common/api/rest-api';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class FilesDataManager extends RestDataManager {
|
||||
constructor() {
|
|
@ -21,12 +21,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { FormGroup, FormControl, ControlLabel, Button, ProgressBar, Col } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel, Button, ProgressBar, Col } from 'react-bootstrap';
|
||||
|
||||
import FileStore from '../stores/file-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import FileStore from './file-store';
|
||||
import UserStore from '../user/user-store';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class SelectFile extends React.Component {
|
||||
static getStores() {
|
||||
|
@ -107,21 +107,21 @@ class SelectFile extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const fileOptions = this.state.files.map(f =>
|
||||
const fileOptions = this.state.files.map(f =>
|
||||
<option key={f._id} value={f._id}>{f.name}</option>
|
||||
);
|
||||
|
||||
const progressBarStyle = {
|
||||
marginLeft: '100px',
|
||||
marginLeft: '100px',
|
||||
marginTop: '-25px'
|
||||
};
|
||||
|
||||
return <div>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
<Col componentClass={FormLabel} sm={3} md={2}>
|
||||
{this.props.name}
|
||||
</Col>
|
||||
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<FormControl disabled={this.props.disabled} componentClass='select' placeholder='Select file' onChange={this.handleChange}>
|
||||
{fileOptions}
|
||||
|
@ -148,4 +148,5 @@ class SelectFile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(SelectFile);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(SelectFile));
|
BIN
src/img/uel.png
Normal file
BIN
src/img/uel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
src/img/uel_efre.jpeg
Normal file
BIN
src/img/uel_efre.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
|
@ -20,9 +20,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditProjectDialog extends React.Component {
|
||||
valid: true;
|
||||
|
@ -80,12 +80,12 @@ class EditProjectDialog extends React.Component {
|
|||
<Dialog show={this.props.show} title="Edit Simulation" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulation">
|
||||
<ControlLabel>Simulation</ControlLabel>
|
||||
<FormLabel>Simulation</FormLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulation" value={this.state.simulation} onChange={(e) => this.handleChange(e)}>
|
||||
{this.props.simulations.map(simulation => (
|
||||
<option key={simulation._id} value={simulation._id}>{simulation.name}</option>
|
|
@ -20,9 +20,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class NewProjectDialog extends React.Component {
|
||||
valid: false;
|
||||
|
@ -82,12 +82,12 @@ class NewProjectDialog extends React.Component {
|
|||
<Dialog show={this.props.show} title="New Project" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulation" validationState={this.validateForm('simulation')}>
|
||||
<ControlLabel>Simulation</ControlLabel>
|
||||
<FormLabel>Simulation</FormLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulation" value={this.state.simulation} onChange={(e) => this.handleChange(e)}>
|
||||
{this.props.simulations.map(simulation => (
|
||||
<option key={simulation._id} value={simulation._id}>{simulation.name}</option>
|
|
@ -19,7 +19,7 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import ProjectsDataManager from '../data-managers/projects-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import ProjectsDataManager from './projects-data-manager';
|
||||
|
||||
export default new ArrayStore('projects', ProjectsDataManager);
|
|
@ -24,24 +24,24 @@ import { Container } from 'flux/utils';
|
|||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import VisualizationStore from '../stores/visualization-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import ProjectStore from './project-store';
|
||||
import UserStore from '../user/user-store';
|
||||
import DashboardStore from '../dashboard/dashboard-store';
|
||||
import SimulationStore from '../simulation/simulation-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import CustomTable from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewVisualzationDialog from '../components/dialogs/new-visualization';
|
||||
import EditVisualizationDialog from '../components/dialogs/edit-visualization';
|
||||
import ImportVisualizationDialog from '../components/dialogs/import-visualization';
|
||||
import Icon from '../common/icon';
|
||||
import CustomTable from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewVisualzationDialog from '../dashboard/new-dashboard';
|
||||
import EditDashboardDialog from '../dashboard/edit-dashboard';
|
||||
import ImportDashboardDialog from '../dashboard/import-dashboard';
|
||||
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Visualizations extends Component {
|
||||
class Dashboards extends Component {
|
||||
static getStores() {
|
||||
return [ ProjectStore, VisualizationStore, UserStore, SimulationStore ];
|
||||
return [ ProjectStore, DashboardStore, UserStore, SimulationStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
|
@ -68,15 +68,15 @@ class Visualizations extends Component {
|
|||
simulation = SimulationStore.getState().find(simulation => simulation._id === project.simulation);
|
||||
}
|
||||
|
||||
// load visualizations
|
||||
let visualizations = [];
|
||||
// load dashboards
|
||||
let dashboards = [];
|
||||
|
||||
if (project.visualizations != null) {
|
||||
visualizations = VisualizationStore.getState().filter(visualization => project.visualizations.includes(visualization._id));
|
||||
if (project.dashboards != null) {
|
||||
dashboards = DashboardStore.getState().filter(dashboard => project.dashboards.includes(dashboard._id));
|
||||
}
|
||||
|
||||
return {
|
||||
visualizations,
|
||||
dashboards,
|
||||
project,
|
||||
simulation,
|
||||
sessionToken,
|
||||
|
@ -91,7 +91,7 @@ class Visualizations extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load',
|
||||
type: 'dashboards/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
|
@ -105,11 +105,11 @@ class Visualizations extends Component {
|
|||
this.setState({ newModal: false });
|
||||
|
||||
if (data) {
|
||||
// add project to visualization
|
||||
// add project to dashboard
|
||||
data.project = this.state.project._id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-add',
|
||||
type: 'dashboards/start-add',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
@ -132,7 +132,7 @@ class Visualizations extends Component {
|
|||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-remove',
|
||||
type: 'dashboards/start-remove',
|
||||
data: this.state.modalData,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
@ -143,7 +143,7 @@ class Visualizations extends Component {
|
|||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-edit',
|
||||
type: 'dashboards/start-edit',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
@ -157,7 +157,7 @@ class Visualizations extends Component {
|
|||
data.project = this.state.project._id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-add',
|
||||
type: 'dashboards/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
@ -172,20 +172,20 @@ class Visualizations extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
exportVisualization(index) {
|
||||
exportDashboard(index) {
|
||||
// filter properties
|
||||
let visualization = Object.assign({}, this.state.visualizations[index]);
|
||||
delete visualization._id;
|
||||
delete visualization.project;
|
||||
delete visualization.user;
|
||||
let dashboard = Object.assign({}, this.state.dashboards[index]);
|
||||
delete dashboard._id;
|
||||
delete dashboard.project;
|
||||
delete dashboard.user;
|
||||
|
||||
visualization.widgets.forEach(widget => {
|
||||
dashboard.widgets.forEach(widget => {
|
||||
delete widget.simulator;
|
||||
});
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(visualization, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'visualization - ' + visualization.name + '.json');
|
||||
const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'dashboard - ' + dashboard.name + '.json');
|
||||
}
|
||||
|
||||
onModalKeyPress = (event) => {
|
||||
|
@ -205,30 +205,31 @@ class Visualizations extends Component {
|
|||
<div className='section'>
|
||||
<h1>{this.state.project.name}</h1>
|
||||
|
||||
<CustomTable data={this.state.visualizations}>
|
||||
<TableColumn title='Name' dataKey='name' link='/visualizations/' linkKey='_id' />
|
||||
<CustomTable data={this.state.dashboards}>
|
||||
<TableColumn title='Name' dataKey='name' link='/dashboards/' linkKey='_id' />
|
||||
<TableColumn
|
||||
width='100'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
onEdit={(index) => this.setState({ editModal: true, modalData: this.state.visualizations[index] })}
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.visualizations[index] })}
|
||||
onExport={index => this.exportVisualization(index)}
|
||||
onEdit={(index) => this.setState({ editModal: true, modalData: this.state.dashboards[index] })}
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.dashboards[index] })}
|
||||
onExport={index => this.exportDashboard(index)}
|
||||
/>
|
||||
</CustomTable>
|
||||
|
||||
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Visualization</Button>
|
||||
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Dashboard</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
|
||||
<NewVisualzationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditVisualizationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} visualization={this.state.modalData} />
|
||||
<ImportVisualizationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} simulation={this.state.simulation} />
|
||||
<EditDashboardDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} dashboard={this.state.modalData} />
|
||||
<ImportDashboardDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} simulation={this.state.simulation} />
|
||||
|
||||
<DeleteDialog title="visualization" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
<DeleteDialog title="dashboard" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Visualizations, {withProps: true});
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Dashboards), {withProps: true});
|
|
@ -19,6 +19,6 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
|
||||
export default new RestDataManager('project', '/projects');
|
|
@ -23,18 +23,18 @@ import React from 'react';
|
|||
import { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import ProjectStore from './project-store';
|
||||
import UserStore from '../user/user-store';
|
||||
import SimulationStore from '../simulation/simulation-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewProjectDialog from '../components/dialogs/new-project';
|
||||
import EditProjectDialog from '../components/dialogs/edit-project';
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewProjectDialog from './new-project';
|
||||
import EditProjectDialog from './edit-project';
|
||||
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Projects extends React.Component {
|
||||
static getStores() {
|
||||
|
@ -156,4 +156,5 @@ class Projects extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(Projects);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Projects));
|
|
@ -22,9 +22,9 @@
|
|||
import React from 'react';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import App from './containers/app';
|
||||
import Login from './containers/login';
|
||||
import Logout from './containers/logout';
|
||||
import App from './app';
|
||||
import Login from './user/login';
|
||||
import Logout from './user/logout';
|
||||
|
||||
class Root extends React.Component {
|
||||
render() {
|
||||
|
|
101
src/scenario/edit-scenario.js
Normal file
101
src/scenario/edit-scenario.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* File: edit-scenario.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel, Col} from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class EditScenarioDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
id: '',
|
||||
running: false,
|
||||
startParameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.valid && this.props.onClose != null) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ [event.target.id]: event.target.value });
|
||||
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
|
||||
};
|
||||
|
||||
resetState = () => {
|
||||
this.setState({
|
||||
name: this.props.scenario.name,
|
||||
id: this.props.scenario.id,
|
||||
running: this.props.scenario.running,
|
||||
startParameters: this.props.scenario.startParameters || {}
|
||||
});
|
||||
};
|
||||
|
||||
handleStartParametersChange = startParameters => {
|
||||
this.setState({ startParameters });
|
||||
};
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title='Edit Scenario' buttonTitle='Save' onClose={this.onClose} onReset={this.resetState} valid={true}>
|
||||
<form>
|
||||
<FormGroup as={Col} controlId='name'>
|
||||
<FormLabel column={false}>Name</FormLabel>
|
||||
<FormControl type='text' placeholder='Enter name' value={this.state.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} controlId='startParameters'>
|
||||
<FormLabel column={false}>Start Parameters</FormLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditScenarioDialog;
|
151
src/scenario/import-scenario.js
Normal file
151
src/scenario/import-scenario.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* File: import-scenario.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel, Col} from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class ImportScenarioDialog extends React.Component {
|
||||
valid = false;
|
||||
imported = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
running: '',
|
||||
simulationModels: [],
|
||||
startParameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.valid && this.props.onClose != null) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e, index) {
|
||||
if (e.target.id === 'simulator') {
|
||||
const models = this.state.models;
|
||||
models[index].simulator = JSON.parse(e.target.value);
|
||||
|
||||
this.setState({ models });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
|
||||
// check all controls
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({ name: '', models: [], startParameters: {} });
|
||||
|
||||
this.imported = false;
|
||||
}
|
||||
|
||||
loadFile = event => {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (!file.type.match('application/json')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create file reader
|
||||
const reader = new FileReader();
|
||||
const self = this;
|
||||
|
||||
reader.onload = onloadEvent => {
|
||||
const scenario = JSON.parse(onloadEvent.target.result);
|
||||
|
||||
// scenario.simulationModels.forEach(model => {
|
||||
// model.simulator = {
|
||||
// node: self.props.nodes[0]._id,
|
||||
// simulator: 0
|
||||
// };
|
||||
// });
|
||||
|
||||
self.imported = true;
|
||||
self.valid = true;
|
||||
self.setState({ name: scenario.name, models: scenario.simulationModels, startParameters: scenario.startParameters, running: scenario.running });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title="Import Scenario" buttonTitle="Import" onClose={this.onClose} onReset={this.resetState} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup as={Col} controlId="file">
|
||||
<FormLabel>Scenario File</FormLabel>
|
||||
<FormControl type="file" onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} controlId="name">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl readOnly={this.imported === false} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col}>
|
||||
<FormLabel>Start Parameters</FormLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} disabled={this.imported === false} />
|
||||
</FormGroup>
|
||||
|
||||
{/* {this.state.models.map((model, index) => (
|
||||
<FormGroup controlId="simulator" key={index}>
|
||||
<FormLabel>{model.name} - Simulator</FormLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: model.simulator.node, simulator: model.simulator.simulator})} onChange={(e) => this.handleChange(e, index)}>
|
||||
{this.props.nodes.map(node => (
|
||||
node.simulators.map((simulator, index) => (
|
||||
<option key={node._id + index} value={JSON.stringify({ node: node._id, simulator: index })}>{node.name}/{simulator.name}</option>
|
||||
))
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
))} */}
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportScenarioDialog;
|
95
src/scenario/new-scenario.js
Normal file
95
src/scenario/new-scenario.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* File: new-scenario.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel, Col} from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class NewScenarioDialog extends React.Component {
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
startParameters: {},
|
||||
running: false
|
||||
};
|
||||
}
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.valid && this.props.onClose != null) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ [event.target.id]: event.target.value });
|
||||
|
||||
// check all controls
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({ name: '', startParameters: {} });
|
||||
}
|
||||
|
||||
handleStartParametersChange = startParameters => {
|
||||
this.setState({ startParameters });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title="New Scenario" buttonTitle="Add" onClose={this.onClose} onReset={this.resetState} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup as={Col} controlId="name">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col}>
|
||||
<FormLabel>Start Parameters</FormLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default NewScenarioDialog;
|
63
src/scenario/scenario-store.js
Normal file
63
src/scenario/scenario-store.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* File: scenario-store.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* 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/>.
|
||||
************************************************************s*****************/
|
||||
|
||||
|
||||
import ScenariosDataManager from './scenarios-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
//import UsersDataManager from "../user/users-data-manager";
|
||||
//import SimulatorDataDataManager from "../simulator/simulator-data-data-manager";
|
||||
//import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
export default new ArrayStore('scenarios', ScenariosDataManager);
|
||||
|
||||
|
||||
// class ScenariosStore extends ReduceStore {
|
||||
// constructor() {
|
||||
// super('scenarios', ScenariosDataManager);
|
||||
// }
|
||||
//
|
||||
// getInitialState() {
|
||||
// return {
|
||||
// scenarios: [],
|
||||
//
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// reduce(state, action) {
|
||||
// switch (action.type) {
|
||||
// case 'scenarios/load-models':
|
||||
// // request simulation model data of scenario
|
||||
// ScenariosDataManager.getSimulationModels(action.token, action.scenarioID);
|
||||
//
|
||||
// return Object.assign({}, state, { token: action.token, simulationmodels: action.simulationmodels});
|
||||
//
|
||||
// case 'scenarios/load-dashboards':
|
||||
// // request dashboard data of scenario
|
||||
// ScenariosDataManager.getDashboards(action.token, action.scenarioID);
|
||||
//
|
||||
// return Object.assign({}, state, { token: action.token, dashboards: action.dashboards});
|
||||
// default:
|
||||
// return state;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export default new ScenariosStore();
|
297
src/scenario/scenario.js
Normal file
297
src/scenario/scenario.js
Normal file
|
@ -0,0 +1,297 @@
|
|||
/**
|
||||
* File: scenario.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
|
||||
import ScenarioStore from './scenario-store';
|
||||
import SimulatorStore from '../simulator/simulator-store';
|
||||
import SimulationModelStore from '../simulationmodel/simulation-model-store';
|
||||
import UserStore from '../user/user-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import ImportSimulationModelDialog from '../simulationmodel/import-simulation-model';
|
||||
|
||||
import SimulatorAction from '../simulator/simulator-action';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Scenario extends React.Component {
|
||||
static getStores() {
|
||||
return [ ScenarioStore, SimulatorStore, SimulationModelStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
// get selected scenario
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
let scenario = ScenarioStore.getState().find(s => s.id === props.match.params.scenario);
|
||||
if (scenario == null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
data: props.match.params.scenario,
|
||||
token: sessionToken
|
||||
});
|
||||
|
||||
scenario = {};
|
||||
}
|
||||
|
||||
// load models
|
||||
let simulationModels = [];
|
||||
if (scenario.simulationModels != null) {
|
||||
simulationModels = SimulationModelStore.getState().filter(m => m != null && scenario.simulationModels.includes(m.id));
|
||||
}
|
||||
|
||||
return {
|
||||
simulationModels,
|
||||
scenario,
|
||||
|
||||
//simulators: SimulatorStore.getState(),
|
||||
sessionToken,
|
||||
|
||||
deleteModal: false,
|
||||
importModal: false,
|
||||
modalData: {},
|
||||
|
||||
selectedSimulationModels: []
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
//TODO users
|
||||
|
||||
//TODO dashboards
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
addSimulationModel = () => {
|
||||
const simulationModel = {
|
||||
scenario: this.state.scenario.id,
|
||||
name: 'New Simulation Model',
|
||||
simulator: this.state.simulators.length > 0 ? this.state.simulators[0].id : null,
|
||||
outputLength: 1,
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputLength: 1,
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
};
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data: simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ scenario: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
data: this.props.match.params.scenario,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-remove',
|
||||
data: this.state.modalData,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
importSimulationModel = simulationModel => {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (simulationModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
simulationModel.scenario = this.state.scenario.id;
|
||||
|
||||
console.log(simulationModel);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data: simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ scenario: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
data: this.props.match.params.scenario,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSimulatorName(simulatorId) {
|
||||
for (let simulator of this.state.simulators) {
|
||||
if (simulator.id === simulatorId) {
|
||||
return _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name') || simulator.uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exportModel(index) {
|
||||
// filter properties
|
||||
const model = Object.assign({}, this.state.simulationModels[index]);
|
||||
|
||||
delete model.simulator;
|
||||
delete model.scenario;
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(model, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'simulation model - ' + model.name + '.json');
|
||||
}
|
||||
|
||||
onSimulationModelChecked(index, event) {
|
||||
const selectedSimulationModels = Object.assign([], this.state.selectedSimulationModels);
|
||||
for (let key in selectedSimulationModels) {
|
||||
if (selectedSimulationModels[key] === index) {
|
||||
// update existing entry
|
||||
if (event.target.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulationModels.splice(key, 1);
|
||||
|
||||
this.setState({ selectedSimulationModels });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new entry
|
||||
if (event.target.checked === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulationModels.push(index);
|
||||
this.setState({ selectedSimulationModels });
|
||||
}
|
||||
|
||||
runAction = action => {
|
||||
for (let index of this.state.selectedSimulationModels) {
|
||||
// get simulator for model
|
||||
let simulator = null;
|
||||
for (let sim of this.state.simulators) {
|
||||
if (sim._id === this.state.simulationModels[index].simulator) {
|
||||
simulator = sim;
|
||||
}
|
||||
}
|
||||
|
||||
if (simulator == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action.data.action === 'start') {
|
||||
action.data.parameters = this.state.simulationModels[index].startParameters;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-action',
|
||||
simulator,
|
||||
data: action.data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
return <div className='section'>
|
||||
<h1>{this.state.simulation.name}</h1>
|
||||
|
||||
<Table data={this.state.simulationModels}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulationModelChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' link='/simulationModel/' linkKey='_id' />
|
||||
<TableColumn title='Simulator' dataKey='simulator' modifier={(simulator) => this.getSimulatorName(simulator)} />
|
||||
<TableColumn title='Output' dataKey='outputLength' width='100' />
|
||||
<TableColumn title='Input' dataKey='inputLength' width='100' />
|
||||
<TableColumn
|
||||
title=''
|
||||
width='70'
|
||||
deleteButton
|
||||
exportButton
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulationModels[index], modalIndex: index })}
|
||||
onExport={index => this.exportModel(index)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<div style={{ float: 'left' }}>
|
||||
<SimulatorAction
|
||||
runDisabled={this.state.selectedSimulationModels.length === 0}
|
||||
runAction={this.runAction}
|
||||
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 style={{ float: 'right' }}>
|
||||
<Button onClick={this.addSimulationModel} style={buttonStyle}><Icon icon="plus" /> Simulation Model</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<ImportSimulationModelDialog show={this.state.importModal} onClose={this.importSimulationModel} simulators={this.state.simulators} />
|
||||
|
||||
<DeleteDialog title="simulation model" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Scenario), { withProps: true });
|
60
src/scenario/scenarios-data-manager.js
Normal file
60
src/scenario/scenarios-data-manager.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* File: scenarios-data-manager.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* 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 RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import RestAPI from "../common/api/rest-api";
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
class ScenariosDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
super('scenario', '/scenarios', ['id', 'name', 'running', 'simulationModelIDs', 'userIDs', 'dashboardIDs', 'startParameters' ]);
|
||||
}
|
||||
|
||||
getSimulationModels(token, id) {
|
||||
RestAPI.get(this.makeURL('/scenarios/' + id + '/models'), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/simulationmodels',
|
||||
simulationmodels: response.models
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/simulationmodels-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getDashboards(token, id) {
|
||||
RestAPI.get(this.makeURL('/scenarios/' + id + '/dashboards'), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/dashboards',
|
||||
dashboards: response.dashboards
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/dashboards-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
export default new ScenariosDataManager();
|
212
src/scenario/scenarios.js
Normal file
212
src/scenario/scenarios.js
Normal file
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* File: scenarios.js
|
||||
* Author: Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2019
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import ScenarioStore from './scenario-store';
|
||||
import UserStore from '../user/user-store';
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewScenarioDialog from './new-scenario';
|
||||
import EditScenarioDialog from './edit-scenario';
|
||||
import ImportScenarioDialog from './import-scenario';
|
||||
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Scenarios extends Component {
|
||||
static getStores() {
|
||||
return [ ScenarioStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
const scenarios = ScenarioStore.getState();
|
||||
const sessionToken = UserStore.getState().token;
|
||||
console.log(scenarios);
|
||||
|
||||
return {
|
||||
scenarios,
|
||||
sessionToken,
|
||||
|
||||
newModal: false,
|
||||
deleteModal: false,
|
||||
editModal: false,
|
||||
importModal: false,
|
||||
modalScenario: {},
|
||||
|
||||
selectedScenarios: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {}
|
||||
|
||||
closeNewModal(data) {
|
||||
this.setState({ newModal : false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showDeleteModal(id) {
|
||||
// get scenario by id
|
||||
var deleteScenario;
|
||||
|
||||
this.state.scenarios.forEach((scenario) => {
|
||||
if (scenario.id === id) {
|
||||
deleteScenario = scenario;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ deleteModal: true, modalScenario: deleteScenario });
|
||||
}
|
||||
|
||||
closeDeleteModal(confirmDelete) {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-remove',
|
||||
data: this.state.modalScenario,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
};
|
||||
|
||||
showEditModal(id) {
|
||||
// get scenario by id
|
||||
var editScenario;
|
||||
|
||||
this.state.scenarios.forEach((scenario) => {
|
||||
if (scenario.id === id) {
|
||||
editScenario = scenario;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ editModal: true, modalScenario: editScenario });
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal : false });
|
||||
|
||||
if (data != null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-edit',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeImportModal(data) {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onModalKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
this.confirmDeleteModal();
|
||||
}
|
||||
};
|
||||
|
||||
exportScenario(index) {
|
||||
// filter properties
|
||||
let scenario = Object.assign({}, this.state.scenarios[index]);
|
||||
delete scenario.id;
|
||||
|
||||
// TODO request missing scenario parameters (Dashboards and Simulation Modles) recursively for export
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(scenario, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'scenario - ' + scenario.name + '.json');
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='section'>
|
||||
<h1>Scenarios</h1>
|
||||
|
||||
<Table data={this.state.scenarios}>
|
||||
<TableColumn title='Name' dataKey='name' link='/scenarios/' linkKey='id' />
|
||||
<TableColumn title='ID' dataKey='id' link='/scenarios/' linkKey='id' />
|
||||
<TableColumn title='Running' dataKey='running' link='/scenarios/' linkKey='id' />
|
||||
<TableColumn
|
||||
width='200'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
onEdit={index => this.setState({ editModal: true, modalScenario: this.state.scenarios[index] })}
|
||||
onDelete={index => this.setState({ deleteModal: true, modalScenario: this.state.scenarios[index] })}
|
||||
onExport={index => this.exportScenario(index)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Scenario</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<NewScenarioDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditScenarioDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} scenario={this.state.modalScenario} />
|
||||
<ImportScenarioDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
|
||||
|
||||
<DeleteDialog title="scenario" name={this.state.modalScenario.name} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Scenarios));
|
|
@ -20,10 +20,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class EditSimulationDialog extends React.Component {
|
||||
valid = true;
|
||||
|
@ -85,13 +85,13 @@ class EditSimulationDialog extends React.Component {
|
|||
return <Dialog show={this.props.show} title='Edit Simulation' buttonTitle='Save' onClose={this.onClose} onReset={this.resetState} valid={true}>
|
||||
<form>
|
||||
<FormGroup controlId='name' validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type='text' placeholder='Enter name' value={this.state.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId='startParameters'>
|
||||
<ControlLabel>Start Parameters</ControlLabel>
|
||||
<FormLabel>Start Parameters</FormLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</FormGroup>
|
|
@ -20,10 +20,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class ImportSimulationDialog extends React.Component {
|
||||
valid = false;
|
||||
|
@ -47,7 +47,7 @@ class ImportSimulationDialog extends React.Component {
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.valid && this.props.onClose != null) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class ImportSimulationDialog extends React.Component {
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
|
@ -119,25 +119,25 @@ class ImportSimulationDialog extends React.Component {
|
|||
return <Dialog show={this.props.show} title="Import Simulation" buttonTitle="Import" onClose={this.onClose} onReset={this.resetState} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="file">
|
||||
<ControlLabel>Simulation File</ControlLabel>
|
||||
<FormLabel>Simulation File</FormLabel>
|
||||
<FormControl type="file" onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl readOnly={this.imported === false} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>Start Parameters</ControlLabel>
|
||||
<FormLabel>Start Parameters</FormLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} disabled={this.imported === false} />
|
||||
</FormGroup>
|
||||
|
||||
{/* {this.state.models.map((model, index) => (
|
||||
<FormGroup controlId="simulator" key={index}>
|
||||
<ControlLabel>{model.name} - Simulator</ControlLabel>
|
||||
<FormLabel>{model.name} - Simulator</FormLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: model.simulator.node, simulator: model.simulator.simulator})} onChange={(e) => this.handleChange(e, index)}>
|
||||
{this.props.nodes.map(node => (
|
||||
node.simulators.map((simulator, index) => (
|
|
@ -20,10 +20,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class NewSimulationDialog extends React.Component {
|
||||
valid = false;
|
||||
|
@ -42,7 +42,7 @@ class NewSimulationDialog extends React.Component {
|
|||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -81,13 +81,13 @@ class NewSimulationDialog extends React.Component {
|
|||
return <Dialog show={this.props.show} title="New Simulation" buttonTitle="Add" onClose={this.onClose} onReset={this.resetState} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>Start Parameters</ControlLabel>
|
||||
<FormLabel>Start Parameters</FormLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</FormGroup>
|
|
@ -19,7 +19,7 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import SimulationsDataManager from '../data-managers/simulations-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import SimulationsDataManager from './simulations-data-manager';
|
||||
|
||||
export default new ArrayStore('simulations', SimulationsDataManager);
|
|
@ -25,19 +25,19 @@ import { Button } from 'react-bootstrap';
|
|||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from './simulation-store';
|
||||
import SimulatorStore from '../simulator/simulator-store';
|
||||
import SimulationModelStore from '../simulationmodel/simulation-model-store';
|
||||
import UserStore from '../user/user-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import ImportSimulationModelDialog from '../components/dialogs/import-simulation-model';
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import ImportSimulationModelDialog from '../simulationmodel/import-simulation-model';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
import SimulatorAction from '../simulator/simulator-action';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Simulation extends React.Component {
|
||||
static getStores() {
|
||||
|
@ -101,7 +101,7 @@ class Simulation extends React.Component {
|
|||
const simulationModel = {
|
||||
simulation: this.state.simulation._id,
|
||||
name: 'New Simulation Model',
|
||||
simulator: this.state.simulators.length > 0 ? this.state.simulators[0]._id : null,
|
||||
simulator: this.state.simulators.length > 0 ? this.state.simulators[0].id : null,
|
||||
outputLength: 1,
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputLength: 1,
|
||||
|
@ -165,7 +165,7 @@ class Simulation extends React.Component {
|
|||
|
||||
getSimulatorName(simulatorId) {
|
||||
for (let simulator of this.state.simulators) {
|
||||
if (simulator._id === simulatorId) {
|
||||
if (simulator.id === simulatorId) {
|
||||
return _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name') || simulator.uuid;
|
||||
}
|
||||
}
|
||||
|
@ -285,4 +285,5 @@ class Simulation extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(Simulation, { withProps: true });
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Simulation), { withProps: true });
|
|
@ -19,6 +19,6 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
|
||||
export default new RestDataManager('simulation', '/simulations', [ '_id', 'name', 'projects', 'models', 'startParameters' ]);
|
|
@ -24,21 +24,21 @@ import { Container } from 'flux/utils';
|
|||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import SimulationStore from './simulation-store';
|
||||
import UserStore from '../user/user-store';
|
||||
import SimulatorStore from '../simulator/simulator-store';
|
||||
import SimulationModelStore from '../simulationmodel/simulation-model-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewSimulationDialog from '../components/dialogs/new-simulation';
|
||||
import EditSimulationDialog from '../components/dialogs/edit-simulation';
|
||||
import ImportSimulationDialog from '../components/dialogs/import-simulation';
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewSimulationDialog from './new-simulation';
|
||||
import EditSimulationDialog from './edit-simulation';
|
||||
import ImportSimulationDialog from './import-simulation';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
import SimulatorAction from '../simulator/simulator-action';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Simulations extends Component {
|
||||
static getStores() {
|
||||
|
@ -325,4 +325,5 @@ class Simulations extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(Simulations);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Simulations));
|
|
@ -20,10 +20,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class ImportSimulationModelDialog extends React.Component {
|
||||
imported = false;
|
||||
|
@ -91,15 +91,15 @@ class ImportSimulationModelDialog extends React.Component {
|
|||
<Dialog show={this.props.show} title="Import Simulation Model" buttonTitle="Import" onClose={this.onClose} onReset={this.resetState} valid={this.imported}>
|
||||
<form>
|
||||
<FormGroup controlId='file'>
|
||||
<ControlLabel>Simulation Model File</ControlLabel>
|
||||
<FormLabel>Simulation Model File</FormLabel>
|
||||
<FormControl type='file' onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId='simulator'>
|
||||
<ControlLabel>Simulator</ControlLabel>
|
||||
<FormLabel>Simulator</FormLabel>
|
||||
<FormControl disabled={this.imported === false} componentClass='select' placeholder='Select simulator' value={this.state.model.simulator} onChange={this.handleSimulatorChange}>
|
||||
{this.props.simulators.map(simulator => (
|
||||
<option key={simulator._id} value={simulator._id}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
<option key={simulator.id} value={simulator.id}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
|
@ -21,11 +21,11 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel, FormText } from 'react-bootstrap';
|
||||
import validator from 'validator';
|
||||
|
||||
import Table from './table';
|
||||
import TableColumn from './table-column';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
|
||||
class SignalMapping extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -98,14 +98,14 @@ class SignalMapping extends React.Component {
|
|||
render() {
|
||||
return <div>
|
||||
<FormGroup validationState={this.validateLength()}>
|
||||
<ControlLabel>{this.props.name} Length</ControlLabel>
|
||||
<FormLabel>{this.props.name} Length</FormLabel>
|
||||
<FormControl name='length' type='number' placeholder='Enter length' defaultValue={this.state.length} min='1' onBlur={this.handleLengthChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>{this.props.name} Mapping</ControlLabel>
|
||||
<HelpBlock>Click <i>name</i> or <i>type</i> cell to edit</HelpBlock>
|
||||
<FormLabel>{this.props.name} Mapping</FormLabel>
|
||||
<FormText>Click <i>name</i> or <i>type</i> cell to edit</FormText>
|
||||
<Table data={this.props.signals}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={this.handleMappingChange} />
|
|
@ -19,7 +19,7 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import SimulationModelsDataManager from '../data-managers/simulation-models-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import SimulationModelsDataManager from './simulation-models-data-manager';
|
||||
|
||||
export default new ArrayStore('simulationModels', SimulationModelsDataManager);
|
|
@ -21,17 +21,17 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button, Col, Form, ControlLabel } from 'react-bootstrap';
|
||||
import { Button, Col, Form, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationModelStore from './simulation-model-store';
|
||||
import UserStore from '../user/user-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
import SelectSimulator from './select-simulator';
|
||||
import SelectFile from './select-file';
|
||||
import SignalMapping from '../components/signal-mapping';
|
||||
import EditableHeader from '../components/editable-header';
|
||||
import ParametersEditor from '../components/parameters-editor';
|
||||
import SelectSimulator from '../simulator/select-simulator';
|
||||
import SelectFile from '../file/select-file';
|
||||
import SignalMapping from './signal-mapping';
|
||||
import EditableHeader from '../common/editable-header';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class SimulationModel extends React.Component {
|
||||
static getStores() {
|
||||
|
@ -39,7 +39,7 @@ class SimulationModel extends React.Component {
|
|||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
const simulationModel = SimulationModelStore.getState().find(m => m._id === props.match.params.simulationModel);
|
||||
const simulationModel = SimulationModelStore.getState().find(m => m.id === props.match.params.simulationModel);
|
||||
|
||||
return {
|
||||
simulationModel: simulationModel || {},
|
||||
|
@ -66,11 +66,11 @@ class SimulationModel extends React.Component {
|
|||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.props.history.push('/simulations/' + this.state.simulationModel.simulation);
|
||||
this.props.history.push('/scenarios/' + this.state.simulationModel.scenarioID);
|
||||
}
|
||||
|
||||
discardChanges = () => {
|
||||
this.props.history.push('/simulations/' + this.state.simulationModel.simulation);
|
||||
this.props.history.push('/scenarios/' + this.state.simulationModel.scenarioID);
|
||||
}
|
||||
|
||||
handleSimulatorChange = simulator => {
|
||||
|
@ -140,15 +140,15 @@ class SimulationModel extends React.Component {
|
|||
<SelectFile disabled type='configuration' name='Configuration' onChange={this.handleConfigurationChange} value={this.state.simulationModel.configuration} />
|
||||
|
||||
<div>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
Start Parameters
|
||||
<Col componentClass={FormLabel} sm={3} md={2}>
|
||||
Start Parameters
|
||||
</Col>
|
||||
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<ParametersEditor content={this.state.simulationModel.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</Col>
|
||||
</div>
|
||||
|
||||
|
||||
</Col>
|
||||
|
||||
<Col xs={12} sm={6}>
|
||||
|
@ -168,4 +168,5 @@ class SimulationModel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(SimulationModel, { withProps: true });
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(SimulationModel), { withProps: true });
|
|
@ -19,8 +19,8 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class SimulationModelDataManager extends RestDataManager {
|
||||
constructor() {
|
|
@ -20,11 +20,11 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class EditSimulatorDialog extends React.Component {
|
||||
valid = true;
|
||||
|
@ -41,7 +41,7 @@ class EditSimulatorDialog extends React.Component {
|
|||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
let data = {};
|
||||
let data = this.props.simulator.properties;
|
||||
|
||||
if (this.state.name != null && this.state.name !== "" && this.state.name !== _.get(this.props.simulator, 'rawProperties.name')) {
|
||||
data.name = this.state.name;
|
||||
|
@ -74,18 +74,18 @@ class EditSimulatorDialog extends React.Component {
|
|||
<Dialog show={this.props.show} title="Edit Simulator" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name">
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder={_.get(this.props.simulator, 'rawProperties.name')} value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormLabel column={false}>Name</FormLabel>
|
||||
<FormControl type="text" placeholder={_.get(this.props.simulator, 'properties.name')} value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="endpoint">
|
||||
<ControlLabel>Endpoint</ControlLabel>
|
||||
<FormControl type="text" placeholder={_.get(this.props.simulator, 'rawProperties.endpoint')} value={this.state.endpoint || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormLabel column={false}>Endpoint</FormLabel>
|
||||
<FormControl type="text" placeholder={_.get(this.props.simulator, 'properties.endpoint')} value={this.state.endpoint || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId='properties'>
|
||||
<ControlLabel>Properties</ControlLabel>
|
||||
<ParametersEditor content={_.merge({}, this.props.simulator.rawProperties, this.props.simulator.properties)} disabled={true} />
|
||||
<FormLabel column={false}>Properties</FormLabel>
|
||||
<ParametersEditor content={_.merge({}, _.get(this.props.simulator, 'rawProperties'), _.get(this.props.simulator, 'properties'))} disabled={true} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
|
@ -20,10 +20,10 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class ImportSimulatorDialog extends React.Component {
|
||||
valid = false;
|
||||
|
@ -42,7 +42,7 @@ class ImportSimulatorDialog extends React.Component {
|
|||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
const data = {
|
||||
const data = {
|
||||
properties: {
|
||||
name: this.state.name
|
||||
},
|
||||
|
@ -83,10 +83,10 @@ class ImportSimulatorDialog extends React.Component {
|
|||
// read simulator
|
||||
const simulator = JSON.parse(event.target.result);
|
||||
self.imported = true;
|
||||
self.setState({
|
||||
name: _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name'),
|
||||
self.setState({
|
||||
name: _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name'),
|
||||
endpoint: _.get(simulator, 'properties.endpoint') || _.get(simulator, 'rawProperties.endpoint'),
|
||||
uuid: simulator.uuid
|
||||
uuid: simulator.uuid
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -118,22 +118,22 @@ class ImportSimulatorDialog extends React.Component {
|
|||
<Dialog show={this.props.show} title="New Simulator" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="file">
|
||||
<ControlLabel>Simulator File</ControlLabel>
|
||||
<FormLabel>Simulator File</FormLabel>
|
||||
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="endpoint">
|
||||
<ControlLabel>Endpoint</ControlLabel>
|
||||
<FormLabel>Endpoint</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="uuid" validationState={this.validateForm('uuid')}>
|
||||
<ControlLabel>UUID</ControlLabel>
|
||||
<FormLabel>UUID</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
@ -143,4 +143,4 @@ class ImportSimulatorDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default ImportSimulatorDialog;
|
||||
export default ImportSimulatorDialog;
|
|
@ -20,9 +20,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class NewSimulatorDialog extends React.Component {
|
||||
valid = false;
|
||||
|
@ -99,17 +99,17 @@ class NewSimulatorDialog extends React.Component {
|
|||
<Dialog show={this.props.show} title="New Simulator" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="endpoint">
|
||||
<ControlLabel>Endpoint</ControlLabel>
|
||||
<FormLabel>Endpoint</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="uuid" validationState={this.validateForm('uuid')}>
|
||||
<ControlLabel>UUID</ControlLabel>
|
||||
<FormLabel>UUID</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
|
@ -21,10 +21,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { FormGroup, FormControl, ControlLabel, Col } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel, Col } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import SimulatorStore from './simulator-store';
|
||||
|
||||
class SelectSimulator extends React.Component {
|
||||
static getStores() {
|
||||
|
@ -46,7 +46,7 @@ class SelectSimulator extends React.Component {
|
|||
let selectedSimulator = nextProps.value;
|
||||
if (selectedSimulator == null) {
|
||||
if (this.state.simulators.length > 0) {
|
||||
selectedSimulator = this.state.simulators[0]._id;
|
||||
selectedSimulator = this.state.simulators[0].id;
|
||||
} else {
|
||||
selectedSimulator = '';
|
||||
}
|
||||
|
@ -60,19 +60,19 @@ class SelectSimulator extends React.Component {
|
|||
|
||||
// send complete simulator to callback
|
||||
if (this.props.onChange != null) {
|
||||
const simulator = this.state.simulators.find(s => s._id === event.target.value);
|
||||
const simulator = this.state.simulators.find(s => s.id === event.target.value);
|
||||
|
||||
this.props.onChange(simulator);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const simulatorOptions = this.state.simulators.map(s =>
|
||||
const simulatorOptions = this.state.simulators.map(s =>
|
||||
<option key={s._id} value={s._id}>{_.get(s, 'properties.name') || _.get(s, 'rawProperties.name') || s.uuid}</option>
|
||||
);
|
||||
|
||||
return <FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
<Col componentClass={FormLabel} sm={3} md={2}>
|
||||
Simulator
|
||||
</Col>
|
||||
|
||||
|
@ -85,4 +85,5 @@ class SelectSimulator extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(SelectSimulator);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(SelectSimulator));
|
|
@ -20,7 +20,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, DropdownButton, MenuItem } from 'react-bootstrap';
|
||||
import { Button, DropdownButton, DropdownItem } from 'react-bootstrap';
|
||||
|
||||
class SimulatorAction extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -50,9 +50,9 @@ class SimulatorAction extends React.Component {
|
|||
|
||||
render() {
|
||||
const actionList = this.props.actions.map(action => (
|
||||
<MenuItem key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
|
||||
<DropdownItem key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
|
||||
{action.title}
|
||||
</MenuItem>
|
||||
</DropdownItem>
|
||||
));
|
||||
|
||||
return <div>
|
|
@ -19,8 +19,8 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import WebsocketAPI from '../api/websocket-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import WebsocketAPI from '../common/api/websocket-api';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
const OFFSET_TYPE = 2;
|
||||
const OFFSET_VERSION = 4;
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import SimulatorDataDataManager from './simulator-data-data-manager';
|
||||
|
||||
const MAX_VALUES = 10000;
|
||||
|
|
@ -21,9 +21,9 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import SimulatorsDataManager from '../data-managers/simulators-data-manager';
|
||||
import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import SimulatorsDataManager from './simulators-data-manager';
|
||||
import SimulatorDataDataManager from './simulator-data-data-manager';
|
||||
|
||||
class SimulatorStore extends ArrayStore {
|
||||
constructor() {
|
||||
|
@ -38,7 +38,7 @@ class SimulatorStore extends ArrayStore {
|
|||
const endpoint = _.get(simulator, 'properties.endpoint') || _.get(simulator, 'rawProperties.endpoint');
|
||||
|
||||
if (endpoint != null && endpoint !== '') {
|
||||
SimulatorDataDataManager.open(endpoint, simulator._id);
|
||||
SimulatorDataDataManager.open(endpoint, simulator.id);
|
||||
} else {
|
||||
// console.warn('Endpoint not found for simulator at ' + endpoint);
|
||||
// console.log(simulator);
|
||||
|
@ -53,7 +53,8 @@ class SimulatorStore extends ArrayStore {
|
|||
const endpoint = _.get(simulator, 'properties.endpoint') || _.get(simulator, 'rawProperties.endpoint');
|
||||
|
||||
if (endpoint != null && endpoint !== '') {
|
||||
SimulatorDataDataManager.update(endpoint, simulator._id);
|
||||
console.log("Updating simulatorid " + simulator.id);
|
||||
SimulatorDataDataManager.update(endpoint, simulator.id);
|
||||
}
|
||||
|
||||
return super.reduce(state, action);
|
|
@ -19,9 +19,9 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import RestAPI from '../common/api/rest-api';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class SimulatorsDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
|
@ -30,7 +30,7 @@ class SimulatorsDataManager extends RestDataManager {
|
|||
|
||||
doActions(simulator, action, token = null) {
|
||||
// TODO: Make only simulator id dependent
|
||||
RestAPI.post(this.makeURL(this.url + '/' + simulator._id), action, token).then(response => {
|
||||
RestAPI.post(this.makeURL(this.url + '/' + simulator.id), action, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/action-started',
|
||||
data: response
|
|
@ -25,19 +25,19 @@ import { Button } from 'react-bootstrap';
|
|||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import SimulatorStore from './simulator-store';
|
||||
import UserStore from '../user/user-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewSimulatorDialog from '../components/dialogs/new-simulator';
|
||||
import EditSimulatorDialog from '../components/dialogs/edit-simulator';
|
||||
import ImportSimulatorDialog from '../components/dialogs/import-simulator';
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewSimulatorDialog from './new-simulator';
|
||||
import EditSimulatorDialog from './edit-simulator';
|
||||
import ImportSimulatorDialog from './import-simulator';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
import SimulatorAction from './simulator-action';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Simulators extends Component {
|
||||
static getStores() {
|
||||
|
@ -66,16 +66,20 @@ class Simulators extends Component {
|
|||
|
||||
static calculateState() {
|
||||
const simulators = SimulatorStore.getState().sort((a, b) => {
|
||||
if (a.state !== b.state)
|
||||
return this.statePrio(a.state) > this.statePrio(b.state);
|
||||
else if (a.name !== b.name)
|
||||
if (a.state !== b.state) {
|
||||
return Simulators.statePrio(a.state) > Simulators.statePrio(b.state);
|
||||
}
|
||||
else if (a.name !== b.name) {
|
||||
return a.name < b.name;
|
||||
else
|
||||
}
|
||||
else {
|
||||
return a.stateUpdatedAt < b.stateUpdatedAt;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
sessionToken: UserStore.getState().token,
|
||||
sessionUserID: UserStore.getState().userid,
|
||||
simulators,
|
||||
modalSimulator: {},
|
||||
deleteModal: false,
|
||||
|
@ -87,7 +91,8 @@ class Simulators extends Component {
|
|||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
|
||||
// Start timer for periodic refresh
|
||||
|
@ -99,10 +104,17 @@ class Simulators extends Component {
|
|||
}
|
||||
|
||||
refresh() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
if (this.state.editModal || this.state.deleteModal){
|
||||
// do nothing since a dialog is open at the moment
|
||||
}
|
||||
else {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -113,7 +125,8 @@ class Simulators extends Component {
|
|||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -123,18 +136,22 @@ class Simulators extends Component {
|
|||
|
||||
if (data) {
|
||||
let simulator = this.state.simulators[this.state.modalIndex];
|
||||
console.log("modalIndex: " + this.state.modalIndex);
|
||||
console.log("Simulator Host:" + simulator.host);
|
||||
console.log("Simulator at index 1: " + this.state.simulators[1].host)
|
||||
simulator.properties = data;
|
||||
this.setState({ simulator });
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-edit',
|
||||
data: simulator,
|
||||
token: this.state.sessionToken
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
closeDeleteModal(confirmDelete){
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
|
@ -144,14 +161,15 @@ class Simulators extends Component {
|
|||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-remove',
|
||||
data: this.state.modalSimulator,
|
||||
token: this.state.sessionToken
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
}
|
||||
|
||||
exportSimulator(index) {
|
||||
// filter properties
|
||||
let simulator = Object.assign({}, this.state.simulators[index]);
|
||||
delete simulator._id;
|
||||
delete simulator.id;
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(simulator, null, 2)], { type: 'application/json' });
|
||||
|
@ -165,7 +183,8 @@ class Simulators extends Component {
|
|||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -201,12 +220,13 @@ class Simulators extends Component {
|
|||
type: 'simulators/start-action',
|
||||
simulator: this.state.simulators[index],
|
||||
data: action.data,
|
||||
token: this.state.sessionToken
|
||||
token: this.state.sessionToken,
|
||||
userid: this.state.sessionUserID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isSimulatorOutdated(simulator) {
|
||||
static isSimulatorOutdated(simulator) {
|
||||
if (!simulator.stateUpdatedAt)
|
||||
return true;
|
||||
|
||||
|
@ -215,10 +235,10 @@ class Simulators extends Component {
|
|||
return Date.now() - new Date(simulator.stateUpdatedAt) > fiveMinutes;
|
||||
}
|
||||
|
||||
stateLabelStyle = (state, simulator) => {
|
||||
static stateLabelStyle(state, simulator){
|
||||
var style = [ 'label' ];
|
||||
|
||||
if (this.isSimulatorOutdated(simulator) && state !== 'shutdown') {
|
||||
if (Simulators.isSimulatorOutdated(simulator) && state !== 'shutdown') {
|
||||
style.push('label-outdated');
|
||||
}
|
||||
|
||||
|
@ -250,7 +270,7 @@ class Simulators extends Component {
|
|||
return style.join(' ');
|
||||
}
|
||||
|
||||
stateUpdateModifier = updatedAt => {
|
||||
static stateUpdateModifier(updatedAt) {
|
||||
const date = new Date(updatedAt);
|
||||
|
||||
return date.toLocaleString('de-DE');
|
||||
|
@ -268,15 +288,15 @@ class Simulators extends Component {
|
|||
<Table data={this.state.simulators}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulatorChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKeys={['properties.name', 'rawProperties.name']} />
|
||||
<TableColumn title='State' labelKey='state' tooltipKey='error' labelModifier={this.stateLabelModifier} labelStyle={this.stateLabelStyle} />
|
||||
<TableColumn title='State' labelKey='state' tooltipKey='error' labelModifier={Simulators.stateLabelModifier} labelStyle={Simulators.stateLabelStyle} />
|
||||
<TableColumn title='Category' dataKeys={['properties.category', 'rawProperties.category']} />
|
||||
<TableColumn title='Type' dataKeys={['properties.type', 'rawProperties.type']} />
|
||||
<TableColumn title='Location' dataKeys={['properties.location', 'rawProperties.location']} />
|
||||
{/* <TableColumn title='Realm' dataKeys={['properties.realm', 'rawProperties.realm']} /> */}
|
||||
<TableColumn title='Host' dataKey='host' />
|
||||
<TableColumn title='Last Update' dataKey='stateUpdatedAt' modifier={this.stateUpdateModifier} />
|
||||
<TableColumn title='Last Update' dataKey='stateUpdatedAt' modifier={Simulators.stateUpdateModifier} />
|
||||
<TableColumn
|
||||
width='100'
|
||||
width='200'
|
||||
editButton
|
||||
exportButton
|
||||
deleteButton
|
||||
|
@ -304,10 +324,11 @@ class Simulators extends Component {
|
|||
<EditSimulatorDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} simulator={this.state.modalSimulator} />
|
||||
<ImportSimulatorDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
|
||||
|
||||
<DeleteDialog title="simulator" name={_.get(this.state.modalSimulator, 'properties.name') || _.get(this.state.modalSimulator, 'rawProperties.name') || 'Unknown'} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
<DeleteDialog title="simulator" name={_.get(this.state.modalSimulator, 'properties.name') || _.get(this.state.modalSimulator, 'rawProperties.name') || 'Unknown'} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Simulators);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Simulators));
|
|
@ -216,7 +216,7 @@ body {
|
|||
}
|
||||
|
||||
/**
|
||||
* Visualizations
|
||||
* Dashboards
|
||||
*/
|
||||
.fullscreen-container {
|
||||
padding: 10px;
|
||||
|
|
|
@ -231,7 +231,7 @@ li.signal-legend::before {
|
|||
span.signal-unit {
|
||||
color: grey;
|
||||
font-style: italic;
|
||||
font-weight: 50%;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
span.signal-unit::before {
|
||||
|
|
152
src/user/edit-own-user.js
Normal file
152
src/user/edit-own-user.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* File: edit-user.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 02.05.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel, Col} from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import UserStore from './user-store';
|
||||
|
||||
|
||||
class EditOwnUserDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
mail: '',
|
||||
role: '',
|
||||
id: '',
|
||||
password: '',
|
||||
oldPassword: '',
|
||||
active: '',
|
||||
confirmpassword: ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
handleChange(e) {
|
||||
let user = UserStore.getState().currentUser;
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
|
||||
// check all controls
|
||||
var username = true;
|
||||
var role = true;
|
||||
var mail = true;
|
||||
var pw = true;
|
||||
var active = true;
|
||||
var oldPassword = true;
|
||||
var confirmpassword = true;
|
||||
|
||||
if (this.state.username === '') {
|
||||
username = false;
|
||||
}
|
||||
|
||||
|
||||
if(this.state.mail === ''){
|
||||
mail = false;
|
||||
}
|
||||
|
||||
if(this.state.password === ''){
|
||||
pw = false;
|
||||
}
|
||||
|
||||
if(this.state.active === ''){
|
||||
active = false;
|
||||
}
|
||||
|
||||
if(this.state.oldPassword === ''){
|
||||
oldPassword = false;
|
||||
}
|
||||
|
||||
if(this.state.confirmpassword === ''){
|
||||
confirmpassword = false;
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
role: user.role,
|
||||
id: user.id
|
||||
});
|
||||
|
||||
// form is valid if any of the fields contain somethig
|
||||
this.valid = username || role || active || oldPassword || mail || pw || confirmpassword;
|
||||
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
//username: this.props.user.username,
|
||||
//mail: this.props.user.mail,
|
||||
role: this.props.user.role,
|
||||
id: this.props.user.id
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup as={Col} controlId="username">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter username" value={this.state.username} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="mail">
|
||||
<FormLabel>E-mail</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter e-mail" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="oldPassword">
|
||||
<FormLabel>Old Password</FormLabel>
|
||||
<FormControl type="password" placeholder="Enter current password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} controlId="password">
|
||||
<FormLabel>New Password</FormLabel>
|
||||
<FormControl type="password" placeholder="Enter password" value={this.state.password} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} controlId="confirmpassword">
|
||||
<FormLabel>Confirm New Password</FormLabel>
|
||||
<FormControl type="password" placeholder="Enter password" value={this.state.confirmpassword} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditOwnUserDialog;
|
158
src/user/edit-user.js
Normal file
158
src/user/edit-user.js
Normal file
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* File: edit-user.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 02.05.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import {FormGroup, FormControl, FormLabel, Col} from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditUserDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
mail: '',
|
||||
role: '',
|
||||
id: '',
|
||||
password: '',
|
||||
active: '',
|
||||
confirmpassword: '',
|
||||
oldPassword: ''
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
|
||||
// check all controls
|
||||
var username = true;
|
||||
var role = true;
|
||||
var mail = true;
|
||||
var pw = true;
|
||||
var active = true;
|
||||
var confirmpassword = true;
|
||||
var oldPW = true;
|
||||
|
||||
|
||||
if (this.state.username === '') {
|
||||
username = false;
|
||||
}
|
||||
|
||||
if (this.state.role === ''){
|
||||
role = false;
|
||||
}
|
||||
|
||||
if(this.state.mail === ''){
|
||||
mail = false;
|
||||
}
|
||||
|
||||
if(this.state.password === ''){
|
||||
pw = false;
|
||||
}
|
||||
|
||||
if(this.state.active === ''){
|
||||
active = false;
|
||||
}
|
||||
|
||||
if(this.state.confirmpassword === ''){
|
||||
confirmpassword = false;
|
||||
}
|
||||
|
||||
if(this.state.oldPassword === ''){
|
||||
oldPW = false;
|
||||
}
|
||||
|
||||
// form is valid if any of the fields contain somethig
|
||||
this.valid = username || role || mail || pw || active || confirmpassword || oldPW;
|
||||
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
//username: this.props.user.username,
|
||||
//mail: this.props.user.mail,
|
||||
role: this.props.user.role,
|
||||
id: this.props.user.id
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup as={Col} controlId="username">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter username" value={this.state.username} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="mail">
|
||||
<FormLabel>E-mail</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter e-mail" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="oldPassword">
|
||||
<FormLabel>Old Password</FormLabel>
|
||||
<FormControl type="password" placeholder="Enter current password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="password">
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl type="password" placeholder="Enter password" value={this.state.password} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="confirmpassword">
|
||||
<FormLabel>Confirm New Password</FormLabel>
|
||||
<FormControl type="password" placeholder="Enter password" value={this.state.confirmpassword} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup as={Col} controlId="role">
|
||||
<FormLabel>Role</FormLabel>
|
||||
<FormControl as="select" placeholder="Select role" value={this.state.role} onChange={(e) => this.handleChange(e)}>
|
||||
<option key='1' value='Admin'>Administrator</option>
|
||||
<option key='2' value='User'>User</option>
|
||||
<option key='3' value='Guest'>Guest</option>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} controlId="active">
|
||||
<FormLabel>Active</FormLabel>
|
||||
<FormControl as="select" placeholder="Select Active state" value={this.state.active} onChange={(e) => this.handleChange(e)}>
|
||||
<option key='1' value='yes'>Yes</option>
|
||||
<option key='2' value='no'>No</option>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditUserDialog;
|
|
@ -20,9 +20,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Form, Button, FormGroup, FormControl, ControlLabel, Col } from 'react-bootstrap';
|
||||
import { Form, Button, FormGroup, FormControl, FormLabel, Col } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class LoginForm extends Component {
|
||||
constructor(props) {
|
||||
|
@ -61,20 +61,16 @@ class LoginForm extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Form horizontal>
|
||||
<Form>
|
||||
<FormGroup controlId="username">
|
||||
<Col componentClass={ControlLabel} sm={2}>
|
||||
Username
|
||||
</Col>
|
||||
<FormLabel column={true} sm={2}>Username</FormLabel>
|
||||
<Col sm={10}>
|
||||
<FormControl type="text" placeholder="Username" onChange={(e) => this.handleChange(e)} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="password">
|
||||
<Col componentClass={ControlLabel} sm={2}>
|
||||
Password
|
||||
</Col>
|
||||
<FormLabel column={true} sm={2}>Password</FormLabel>
|
||||
<Col sm={10}>
|
||||
<FormControl type="password" placeholder="Password" onChange={(e) => this.handleChange(e)} />
|
||||
</Col>
|
||||
|
@ -82,14 +78,14 @@ class LoginForm extends Component {
|
|||
|
||||
{this.props.loginMessage &&
|
||||
<div style={{ marginBottom: '50px', color: 'red' }}>
|
||||
<Col smOffset={2} sm={10} style={{ paddingLeft: '5px' }}>
|
||||
<Col sm={{span: 10, offset: 2}} style={{ paddingLeft: '5px' }}>
|
||||
<i>Error: </i>{this.props.loginMessage}
|
||||
</Col>
|
||||
</div>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<Col smOffset={2} sm={10}>
|
||||
<Col sm={{span: 10, offset: 2}}>
|
||||
<Button type="submit" disabled={this.state.disableLogin} onClick={e => this.login(e)}>Login</Button>
|
||||
</Col>
|
||||
</FormGroup>
|
|
@ -21,17 +21,17 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { PageHeader } from 'react-bootstrap';
|
||||
import { NavbarBrand } from 'react-bootstrap';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import LoginForm from '../components/login-form';
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
import LoginForm from './login-form';
|
||||
import Header from '../common/header';
|
||||
import Footer from '../common/footer';
|
||||
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UserStore from './user-store';
|
||||
|
||||
class Login extends Component {
|
||||
static getStores() {
|
||||
|
@ -42,7 +42,8 @@ class Login extends Component {
|
|||
return {
|
||||
currentUser: UserStore.getState().currentUser,
|
||||
token: UserStore.getState().token,
|
||||
loginMessage: UserStore.getState().loginMessage
|
||||
loginMessage: UserStore.getState().loginMessage,
|
||||
userid: UserStore.getState().userid
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -54,11 +55,13 @@ class Login extends Component {
|
|||
// if token stored locally, request user
|
||||
if (nextState.token == null) {
|
||||
const token = localStorage.getItem('token');
|
||||
const userid = localStorage.getItem('userid');
|
||||
|
||||
if (token != null && token !== '' && nextState.currentUser == null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token
|
||||
token: token,
|
||||
userid: userid
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -66,6 +69,7 @@ class Login extends Component {
|
|||
if (nextState.currentUser != null) {
|
||||
// save login in local storage
|
||||
localStorage.setItem('token', nextState.token);
|
||||
localStorage.setItem('userid', nextState.userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +86,7 @@ class Login extends Component {
|
|||
<Header />
|
||||
|
||||
<div className="login-container">
|
||||
<PageHeader>Login</PageHeader>
|
||||
<NavbarBrand>Login</NavbarBrand>
|
||||
|
||||
<LoginForm loginMessage={this.state.loginMessage} />
|
||||
</div>
|
||||
|
@ -93,4 +97,5 @@ class Login extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Container.create(Login);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Login));
|
|
@ -22,7 +22,7 @@
|
|||
import React from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class Logout extends React.Component {
|
||||
componentWillMount() {
|
|
@ -20,9 +20,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel, FormText, Col } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class NewUserDialog extends React.Component {
|
||||
valid: false;
|
||||
|
@ -33,8 +33,8 @@ class NewUserDialog extends React.Component {
|
|||
this.state = {
|
||||
username: '',
|
||||
mail: '',
|
||||
role: 'admin',
|
||||
password: ''
|
||||
role: 'User',
|
||||
password: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,61 +50,52 @@ class NewUserDialog extends React.Component {
|
|||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
|
||||
// check all controls
|
||||
let username = this.state.username !== '' && this.state.username.length >= 3;
|
||||
let password = this.state.password !== '';
|
||||
let role = this.state.role !== '';
|
||||
let mail = this.state.mail !== '';
|
||||
|
||||
this.valid = username && password && role && mail;
|
||||
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
username: '',
|
||||
mail: '',
|
||||
role: 'admin',
|
||||
password: ''
|
||||
role: 'User',
|
||||
password: '',
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
let username = this.state.username !== '' && this.state.username.length >= 3;
|
||||
let password = this.state.password !== '';
|
||||
|
||||
this.valid = username && password;
|
||||
|
||||
// return state to control
|
||||
switch(target) {
|
||||
case 'username':
|
||||
return username ? "success" : "error";
|
||||
case 'password':
|
||||
return password ? "success" : "error";
|
||||
default:
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New user" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="username" validationState={this.validateForm('username')}>
|
||||
<ControlLabel>Username</ControlLabel>
|
||||
<FormGroup as={Col} controlId="username">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter username" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
<HelpBlock>Min 3 characters.</HelpBlock>
|
||||
<FormText>Min 3 characters.</FormText>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="mail">
|
||||
<ControlLabel>E-mail</ControlLabel>
|
||||
<FormGroup as={Col} controlId="mail">
|
||||
<FormLabel>E-mail</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter e-mail" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="password" validationState={this.validateForm('password')}>
|
||||
<ControlLabel>Password</ControlLabel>
|
||||
<FormGroup as={Col} controlId="password">
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter password" value={this.state.password} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="role" validationState={this.validateForm('role')}>
|
||||
<ControlLabel>Role</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select role" value={this.state.role} onChange={(e) => this.handleChange(e)}>
|
||||
<option key='1' value='admin'>Administrator</option>
|
||||
<option key='2' value='user'>User</option>
|
||||
<option key='3' value='guest'>Guest</option>
|
||||
<FormGroup as={Col} controlId="role">
|
||||
<FormLabel>Role</FormLabel>
|
||||
<FormControl as="select" placeholder="Select role" value={this.state.role} onChange={(e) => this.handleChange(e)}>
|
||||
<option key='1' value='Admin'>Administrator</option>
|
||||
<option key='2' value='User'>User</option>
|
||||
<option key='3' value='Guest'>Guest</option>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</form>
|
|
@ -21,9 +21,9 @@
|
|||
|
||||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UsersDataManager from '../data-managers/users-data-manager';
|
||||
import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UsersDataManager from './users-data-manager';
|
||||
import SimulatorDataDataManager from '../simulator/simulator-data-data-manager';
|
||||
|
||||
class UserStore extends ReduceStore {
|
||||
constructor() {
|
||||
|
@ -35,6 +35,7 @@ class UserStore extends ReduceStore {
|
|||
users: [],
|
||||
currentUser: null,
|
||||
token: null,
|
||||
userid: 0,
|
||||
loginMessage: null
|
||||
};
|
||||
}
|
||||
|
@ -53,14 +54,14 @@ class UserStore extends ReduceStore {
|
|||
return Object.assign({}, state, { token: null, currentUser: null });
|
||||
|
||||
case 'users/logged-in':
|
||||
// request logged-in user data
|
||||
UsersDataManager.getCurrentUser(action.token);
|
||||
// // request logged-in user data
|
||||
UsersDataManager.getCurrentUser(action.token, action.userid);
|
||||
|
||||
return Object.assign({}, state, { token: action.token });
|
||||
return Object.assign({}, state, { token: action.token, userid: action.userid});
|
||||
|
||||
case 'users/current-user':
|
||||
// save logged-in user
|
||||
return Object.assign({}, state, { currentUser: action.user });
|
||||
// // save logged-in user
|
||||
return Object.assign({}, state, { currentUser: action.user});
|
||||
|
||||
case 'users/current-user-error':
|
||||
// discard user token
|
||||
|
@ -72,7 +73,7 @@ class UserStore extends ReduceStore {
|
|||
state = Object.assign({}, state, { loginMessage: 'Wrong credentials! Please try again.' });
|
||||
}
|
||||
|
||||
return state;
|
||||
return state;
|
||||
|
||||
default:
|
||||
return state;
|
144
src/user/user.js
Normal file
144
src/user/user.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* File: user.js
|
||||
* Author: Sonja Happ
|
||||
* Date: 18.09.2019
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import {Button, Col, Row} from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UserStore from './user-store';
|
||||
import UsersStore from './users-store';
|
||||
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import EditOwnUserDialog from './edit-own-user'
|
||||
|
||||
|
||||
class User extends Component {
|
||||
static getStores() {
|
||||
return [ UserStore, UsersStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
//prevState = prevState || {};
|
||||
|
||||
let sessionToken = UserStore.getState().token;
|
||||
let user = UserStore.getState().currentUser;
|
||||
|
||||
|
||||
if(user === null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-load',
|
||||
data: UserStore.getState().userid,
|
||||
token: sessionToken
|
||||
});
|
||||
|
||||
user = {};
|
||||
}
|
||||
|
||||
console.log("extracted user 2: " + user.username);
|
||||
|
||||
return {
|
||||
user,
|
||||
|
||||
token: sessionToken,
|
||||
newModal: false,
|
||||
editModal: false,
|
||||
update: false,
|
||||
modalData: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal: false });
|
||||
console.log(data);
|
||||
|
||||
if (data) {
|
||||
if(data.password === data.confirmpassword){
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-own-edit',
|
||||
data: data,
|
||||
token: this.state.token
|
||||
});
|
||||
}
|
||||
|
||||
else{
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/confirm-pw-doesnt-match',
|
||||
data: data,
|
||||
token: this.state.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getHumanRoleName(role_key) {
|
||||
const HUMAN_ROLE_NAMES = {Admin: 'Administrator', User: 'User', Guest: 'Guest'};
|
||||
|
||||
return HUMAN_ROLE_NAMES.hasOwnProperty(role_key)? HUMAN_ROLE_NAMES[role_key] : '';
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Your User Account</h1>
|
||||
|
||||
<form>
|
||||
<Row>
|
||||
<Col xs={3}>Username: </Col>
|
||||
<Col xs={3}> {this.state.user.username} </Col>
|
||||
</Row>
|
||||
|
||||
|
||||
<Row as={Col} >
|
||||
<Col xs={3}>E-mail: </Col>
|
||||
<Col xs={3}> {this.state.user.mail} </Col>
|
||||
</Row>
|
||||
|
||||
<Row as={Col} >
|
||||
<Col xs={3}>Role: </Col>
|
||||
<Col xs={3}> {this.state.user.role} </Col>
|
||||
</Row>
|
||||
|
||||
|
||||
<Button onClick={() => this.setState({ editModal: true })}><Icon icon='edit' /> Edit</Button>
|
||||
|
||||
<EditOwnUserDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} user={this.state.modalData} />
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(User));
|
|
@ -19,9 +19,9 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import RestAPI from '../common/api/rest-api';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class UsersDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
|
@ -32,7 +32,9 @@ class UsersDataManager extends RestDataManager {
|
|||
RestAPI.post(this.makeURL('/authenticate'), { username: username, password: password }).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: response.token
|
||||
token: response.token,
|
||||
user: response.user,
|
||||
userid: response.user.id
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
|
@ -42,8 +44,8 @@ class UsersDataManager extends RestDataManager {
|
|||
});
|
||||
}
|
||||
|
||||
getCurrentUser(token) {
|
||||
RestAPI.get(this.makeURL('/users/me'), token).then(response => {
|
||||
getCurrentUser(token, id) {
|
||||
RestAPI.get(this.makeURL('/users/' + id), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/current-user',
|
||||
user: response.user
|
||||
|
@ -55,7 +57,7 @@ class UsersDataManager extends RestDataManager {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default new UsersDataManager();
|
|
@ -19,9 +19,9 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import UsersDataManager from '../data-managers/users-data-manager';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
import ArrayStore from '../common/array-store';
|
||||
import UsersDataManager from './users-data-manager';
|
||||
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
|
||||
|
||||
class UsersStore extends ArrayStore {
|
||||
constructor() {
|
||||
|
@ -38,7 +38,7 @@ class UsersStore extends ArrayStore {
|
|||
title: 'Failed to add new user',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
}
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_ADD_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
|
@ -51,11 +51,11 @@ class UsersStore extends ArrayStore {
|
|||
title: 'Failed to edit user',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
}
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_EDIT_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
return super.reduce(state, action);
|
||||
|
||||
default:
|
||||
return super.reduce(state, action);
|
|
@ -23,17 +23,17 @@ import React, { Component } from 'react';
|
|||
import { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/user-store';
|
||||
import UsersStore from '../stores/users-store';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UserStore from './user-store';
|
||||
import UsersStore from './users-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewUserDialog from '../components/dialogs/new-user';
|
||||
import EditUserDialog from '../components/dialogs/edit-user';
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewUserDialog from './new-user';
|
||||
import EditUserDialog from './edit-user';
|
||||
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
||||
class Users extends Component {
|
||||
static getStores() {
|
||||
|
@ -75,7 +75,7 @@ class Users extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
closeDeleteModal(confirmDelete) {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
|
@ -93,16 +93,27 @@ class Users extends Component {
|
|||
this.setState({ editModal: false });
|
||||
|
||||
if (data) {
|
||||
if(data.password === data.confirmpassword){
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-edit',
|
||||
data: data,
|
||||
token: this.state.token
|
||||
});
|
||||
}
|
||||
|
||||
else{
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/confirm-pw-doesnt-match',
|
||||
data: data,
|
||||
token: this.state.token
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getHumanRoleName(role_key) {
|
||||
const HUMAN_ROLE_NAMES = {admin: 'Administrator', user: 'User', guest: 'Guest'};
|
||||
const HUMAN_ROLE_NAMES = {Admin: 'Administrator', User: 'User', Guest: 'Guest'};
|
||||
|
||||
return HUMAN_ROLE_NAMES.hasOwnProperty(role_key)? HUMAN_ROLE_NAMES[role_key] : '';
|
||||
}
|
||||
|
@ -113,7 +124,7 @@ class Users extends Component {
|
|||
|
||||
this.confirmDeleteModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
|
@ -123,9 +134,11 @@ class Users extends Component {
|
|||
|
||||
<Table data={this.state.users}>
|
||||
<TableColumn title='Username' width='150' dataKey='username' />
|
||||
<TableColumn title='ID' width='150' dataKey='id' />
|
||||
<TableColumn title='E-mail' dataKey='mail' />
|
||||
<TableColumn title='Role' dataKey='role' modifier={(role) => this.getHumanRoleName(role)} />
|
||||
<TableColumn width='70' editButton deleteButton onEdit={index => this.setState({ editModal: true, modalData: this.state.users[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.users[index] })} />
|
||||
<TableColumn title='Active' dataKey='active' />
|
||||
<TableColumn width='200' editButton deleteButton onEdit={index => this.setState({ editModal: true, modalData: this.state.users[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.users[index] })} />
|
||||
</Table>
|
||||
|
||||
<Button onClick={() => this.setState({ newModal: true })}><Icon icon='plus' /> User</Button>
|
||||
|
@ -133,10 +146,11 @@ class Users extends Component {
|
|||
<NewUserDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditUserDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} user={this.state.modalData} />
|
||||
|
||||
<DeleteDialog title="user" name={this.state.modalData.username} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
<DeleteDialog title="user" name={this.state.modalData.username} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Users);
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Container.create(fluxContainerConverter.convert(Users));
|
|
@ -20,7 +20,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, Checkbox } from 'react-bootstrap';
|
||||
import { FormGroup, FormCheck } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetAspectControl extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -40,10 +40,10 @@ class EditWidgetAspectControl extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<FormGroup>
|
||||
<Checkbox id="lockAspect" checked={this.state.widget.lockAspect} onChange={e => this.props.handleChange(e)}>Lock Aspect</Checkbox>
|
||||
<FormCheck id="lockAspect" checked={this.state.widget.lockAspect} onChange={e => this.props.handleChange(e)}>Lock Aspect</FormCheck>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetAspectControl;
|
||||
export default EditWidgetAspectControl;
|
|
@ -20,7 +20,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, Checkbox } from 'react-bootstrap';
|
||||
import { FormGroup, FormCheck } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetCheckboxControl extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -37,9 +37,9 @@ class EditWidgetCheckboxControl extends React.Component {
|
|||
|
||||
render() {
|
||||
return <FormGroup>
|
||||
<Checkbox id={this.props.controlId} checked={this.state.widget[this.props.controlId] || ''} onChange={e => this.props.handleChange(e)}>{this.props.text}</Checkbox>
|
||||
<FormCheck id={this.props.controlId} checked={this.state.widget[this.props.controlId] || ''} onChange={e => this.props.handleChange(e)}>{this.props.text}</FormCheck>
|
||||
</FormGroup>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetCheckboxControl;
|
||||
export default EditWidgetCheckboxControl;
|
|
@ -21,16 +21,18 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormGroup, Col, Row, Radio, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, Col, Row, FormCheck, FormLabel } from 'react-bootstrap';
|
||||
import classNames from 'classnames';
|
||||
import { scaleOrdinal, schemeCategory20 } from 'd3-scale';
|
||||
import { scaleOrdinal } from 'd3-scale';
|
||||
import {schemeCategory10} from 'd3-scale-chromatic'
|
||||
// schemeCategory20 no longer available in d3
|
||||
|
||||
class EditWidgetColorControl extends Component {
|
||||
|
||||
static get ColorPalette() {
|
||||
let colorCount = 0;
|
||||
const colors = [];
|
||||
const colorScale = scaleOrdinal(schemeCategory20);
|
||||
const colorScale = scaleOrdinal(schemeCategory10);
|
||||
while (colorCount < 20) { colors.push(colorScale(colorCount)); colorCount++; }
|
||||
colors.unshift('#000', '#FFF'); // include black and white
|
||||
|
||||
|
@ -55,7 +57,7 @@ class EditWidgetColorControl extends Component {
|
|||
return (
|
||||
<FormGroup bsClass="color-control">
|
||||
<Row>
|
||||
<Col componentClass={ControlLabel} style={{whiteSpace: 'nowrap' }} sm={2}>
|
||||
<Col componentClass={FormLabel} style={{whiteSpace: 'nowrap' }} sm={2}>
|
||||
{ this.props.label }
|
||||
</Col>
|
||||
<Col sm={10} bsClass='colors-column'>
|
||||
|
@ -70,7 +72,7 @@ class EditWidgetColorControl extends Component {
|
|||
'checked': idx === this.state.widget[this.props.controlId]
|
||||
});
|
||||
|
||||
return (<Radio key={idx} name={this.props.controlId} style={colorStyle} className={checkedClass} value={idx} inline onChange={(e) => this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />)
|
||||
return (<FormCheck type='radio' key={idx} name={this.props.controlId} style={colorStyle} className={checkedClass} value={idx} inline onChange={(e) => this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -20,12 +20,12 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, ControlLabel, Button } from 'react-bootstrap';
|
||||
import { FormGroup, FormLabel, Button } from 'react-bootstrap';
|
||||
|
||||
|
||||
import Icon from '../icon';
|
||||
import Table from '../table';
|
||||
import TableColumn from '../table-column';
|
||||
import Icon from '../common/icon';
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
|
||||
class EditWidgetColorZonesControl extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -115,7 +115,7 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
|
||||
render() {
|
||||
return <FormGroup>
|
||||
<ControlLabel>Color zones</ControlLabel>
|
||||
<FormLabel>Color zones</FormLabel>
|
||||
|
||||
<Table data={this.state.widget.zones}>
|
||||
<TableColumn width="20" checkbox onChecked={this.checkedCell} />
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue