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

Merge branch 'new-api' into develop

This commit is contained in:
Sonja Happ 2019-11-04 16:02:44 +01:00
commit 7954949481
136 changed files with 4784 additions and 2993 deletions

2653
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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
View 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));

View 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;
}
};

View file

@ -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) {

View file

@ -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;
}

View file

@ -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]

View file

@ -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>;
}

View file

@ -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}>&times;</Button>
<Button className="closeButton" variant="link" onClick={this.props.onClose}>&times;</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>

View file

@ -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>
);
}

View file

@ -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 Unions 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" />

View file

@ -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>

View file

@ -36,7 +36,10 @@ class TableColumn extends Component {
clickable: false,
labelKey: null,
checkbox: false,
checkboxKey: ''
checkboxKey: '',
labelStyle: null,
labelModifier: null
};
render() {

View file

@ -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>&nbsp;<Label bsClass={child.props.labelStyle(data[labelKey], data)}>{labelContent.toString()}</Label></span>);
cell.push(<span>
&nbsp;
<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;

View file

@ -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;

View file

@ -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'
}
}

View file

@ -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));

View file

@ -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 }));

View file

@ -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 });

View 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;

View file

@ -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
View 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 }));

View file

@ -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');

View file

@ -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;

View file

@ -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;

View file

@ -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>

View file

@ -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) {

View 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;

View 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

View 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;

View file

@ -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() {

View file

@ -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() {

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src/img/uel_efre.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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});

View file

@ -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');

View file

@ -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));

View file

@ -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() {

View 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;

View 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;

View 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;

View 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
View 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 });

View 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
View 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));

View file

@ -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>

View file

@ -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) => (

View file

@ -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>

View file

@ -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);

View file

@ -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 });

View file

@ -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' ]);

View file

@ -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));

View file

@ -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>

View file

@ -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} />

View file

@ -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);

View file

@ -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 });

View file

@ -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() {

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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));

View file

@ -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>

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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));

View file

@ -216,7 +216,7 @@ body {
}
/**
* Visualizations
* Dashboards
*/
.fullscreen-container {
padding: 10px;

View file

@ -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
View 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
View 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;

View file

@ -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>

View file

@ -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));

View file

@ -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() {

View file

@ -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>

View file

@ -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
View 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));

View file

@ -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();

View file

@ -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);

View file

@ -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));

View file

@ -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;

View file

@ -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;

View file

@ -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}})} />)
}
)
}

View file

@ -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