diff --git a/package.json b/package.json index 2b98174..c423476 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,12 @@ "dependencies": { "bootstrap": "^3.3.7", "classnames": "^2.2.5", + "d3-scale": "^1.0.5", "es6-promise": "^4.0.5", "flux": "^3.1.2", + "gaugeJS": "^1.3.2", "immutable": "^3.8.1", + "rc-slider": "^7.0.1", "rd3": "^0.7.4", "react": "^15.4.2", "react-bootstrap": "^0.30.7", @@ -19,12 +22,8 @@ "react-notification-system": "^0.2.13", "react-rnd": "^4.2.2", "react-router": "^3.0.2", - "superagent": "^3.5.0", - "gaugeJS": "^1.3.2", - "d3-scale": "^1.0.5", - "rc-slider": "^7.0.1", - "prop-types": "^15.0.0", - "d3": "^3.5.0" + "react-sortable-tree": "^0.1.19", + "superagent": "^3.5.0" }, "devDependencies": { "chai": "^3.5.0", diff --git a/src/components/dialog/edit-node.js b/src/components/dialog/edit-node.js new file mode 100644 index 0000000..b6475c3 --- /dev/null +++ b/src/components/dialog/edit-node.js @@ -0,0 +1,98 @@ +/** + * File: edit-node.js + * Author: Markus Grigull + * Date: 06.07.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 . + ******************************************************************************/ + +import React from 'react'; +import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; + +import Dialog from './dialog'; + +class NewNodeDialog extends React.Component { + valid: false; + + constructor(props) { + super(props); + + this.state = { + name: '', + endpoint: '', + config: {}, + simulators: [], + _id: '' + }; + } + + onClose(canceled) { + if (canceled === false) { + this.props.onClose(this.state); + } else { + this.props.onClose(); + } + } + + handleChange(e) { + this.setState({ [e.target.id]: e.target.value }); + } + + resetState() { + this.setState({ name: this.props.node.name, endpoint: this.props.node.endpoint, config: this.props.node.config, simulators: this.props.node.simulators, _id: this.props.node._id }); + } + + validateForm(target) { + // check all controls + var endpoint = true; + var name = true; + + if (this.state.name === '' || this.props.nodes.find(node => node._id !== this.state._id && node.name === this.state.name) !== undefined) { + name = false; + } + + if (this.state.endpoint === '' || this.props.nodes.find(node => node._id !== this.state._id && node.endpoint === this.state.endpoint) !== undefined) { + endpoint = false; + } + + this.valid = endpoint && name; + + // return state to control + if (target === 'name') return name ? "success" : "error"; + else return endpoint ? "success" : "error"; + } + + render() { + return ( + this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + + + + Endpoint + this.handleChange(e)} /> + + +
+
+ ); + } +} + +export default NewNodeDialog; diff --git a/src/components/dialog/edit-simulation-model.js b/src/components/dialog/edit-simulation-model.js index 3de81dd..de5855d 100644 --- a/src/components/dialog/edit-simulation-model.js +++ b/src/components/dialog/edit-simulation-model.js @@ -31,7 +31,7 @@ class EditSimulationModelDialog extends Component { show: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, data: PropTypes.object.isRequired, - simulators: PropTypes.array.isRequired + nodes: PropTypes.array.isRequired }; valid: false; @@ -41,8 +41,9 @@ class EditSimulationModelDialog extends Component { this.state = { name: '', - simulator: '', - length: 1 + simulator: { node: '', simulator: '' }, + length: 1, + mapping: [{ name: 'Signal', type: 'Type' }] } } @@ -68,7 +69,12 @@ class EditSimulationModelDialog extends Component { } } - this.setState({ [e.target.id]: e.target.value }); + if (e.target.id === 'simulator') { + var value = e.target.value.split("/"); + this.setState({ simulator: { node: value[0], simulator: value[1] } }); + } else { + this.setState({ [e.target.id]: e.target.value }); + } } handleMappingChange(event, row, column) { @@ -124,9 +130,11 @@ class EditSimulationModelDialog extends Component { Simulator - this.handleChange(e)}> - {this.props.simulators.map(simulator => ( - + this.handleChange(e)}> + {this.props.nodes.map(node => ( + node.simulators.map((simulator, index) => ( + + )) ))} diff --git a/src/components/dialog/edit-simulator.js b/src/components/dialog/edit-simulator.js index 3f7f64e..f7f6d76 100644 --- a/src/components/dialog/edit-simulator.js +++ b/src/components/dialog/edit-simulator.js @@ -37,9 +37,7 @@ class EditSimulatorDialog extends Component { super(props); this.state = { - name: '', - endpoint: '', - _id: '' + name: '' }; } @@ -57,30 +55,22 @@ class EditSimulatorDialog extends Component { resetState() { this.setState({ - name: this.props.simulator.name, - endpoint: this.props.simulator.endpoint, - _id: this.props.simulator._id + name: this.props.simulator.name }); } validateForm(target) { // check all controls - var endpoint = true; var name = true; - if (this.state.name === '') { + if (this.state.name === '' || this.props.node.simulators.find(simulator => this.props.simulator.name !== this.state.name && simulator.name === this.state.name) !== undefined) { name = false; } - if (this.state.endpoint === '') { - endpoint = false; - } - - this.valid = endpoint && name; + this.valid = name; // return state to control if (target === 'name') return name ? "success" : "error"; - else return endpoint ? "success" : "error"; } render() { @@ -92,11 +82,6 @@ class EditSimulatorDialog extends Component { this.handleChange(e)} /> - - Endpoint - this.handleChange(e)} /> - - ); diff --git a/src/components/dialog/edit-widget-signal-control.js b/src/components/dialog/edit-widget-signal-control.js index 623b416..98d9b06 100644 --- a/src/components/dialog/edit-widget-signal-control.js +++ b/src/components/dialog/edit-widget-signal-control.js @@ -28,7 +28,7 @@ class EditWidgetSignalControl extends Component { this.state = { widget: { - simulator: '' + simulator: {} } }; } @@ -43,10 +43,10 @@ class EditWidgetSignalControl extends Component { if (this.props.simulation) { // get selected simulation model - const simulationModel = this.props.simulation.models.find( model => model.simulator === this.state.widget.simulator ); + const simulationModel = this.props.simulation.models.find( model => model.simulator.node === this.state.widget.simulator.node && model.simulator.simulator === this.state.widget.simulator.simulator ); // If simulation model update the signals to render - signalsToRender = simulationModel? simulationModel.mapping : []; + signalsToRender = simulationModel ? simulationModel.mapping : []; } return ( @@ -68,4 +68,4 @@ class EditWidgetSignalControl extends Component { } } -export default EditWidgetSignalControl; \ No newline at end of file +export default EditWidgetSignalControl; diff --git a/src/components/dialog/edit-widget-signals-control.js b/src/components/dialog/edit-widget-signals-control.js index 76b832b..c52b928 100644 --- a/src/components/dialog/edit-widget-signals-control.js +++ b/src/components/dialog/edit-widget-signals-control.js @@ -28,7 +28,7 @@ class EditWidgetSignalsControl extends Component { this.state = { widget: { - simulator: '' + simulator: {} } }; } @@ -58,12 +58,12 @@ class EditWidgetSignalsControl extends Component { if (this.props.simulation) { // get selected simulation model - const simulationModel = this.props.simulation.models.find( model => model.simulator === this.state.widget.simulator ); + const simulationModel = this.props.simulation.models.find( model => model.simulator.node === this.state.widget.simulator.node && model.simulator.simulator === this.state.widget.simulator.simulator ); // If simulation model update the signals to render signalsToRender = simulationModel? simulationModel.mapping : []; } - + return ( Signals @@ -81,4 +81,4 @@ class EditWidgetSignalsControl extends Component { } } -export default EditWidgetSignalsControl; \ No newline at end of file +export default EditWidgetSignalsControl; diff --git a/src/components/dialog/edit-widget-simulator-control.js b/src/components/dialog/edit-widget-simulator-control.js index 48186ef..ec4cec5 100644 --- a/src/components/dialog/edit-widget-simulator-control.js +++ b/src/components/dialog/edit-widget-simulator-control.js @@ -28,7 +28,7 @@ class EditWidgetSimulatorControl extends Component { this.state = { widget: { - simulator: '' + simulator: {} } }; } @@ -39,17 +39,16 @@ class EditWidgetSimulatorControl extends Component { } render() { - return ( - Simulator - this.props.handleChange(e)}> + Simulation Model + this.props.handleChange(e)}> { this.props.simulation.models.length === 0? ( - + ) : ( this.props.simulation.models.map((model, index) => ( - + ))) } @@ -58,4 +57,4 @@ class EditWidgetSimulatorControl extends Component { } } -export default EditWidgetSimulatorControl; \ No newline at end of file +export default EditWidgetSimulatorControl; diff --git a/src/components/dialog/edit-widget.js b/src/components/dialog/edit-widget.js index cd036ca..398fd5a 100644 --- a/src/components/dialog/edit-widget.js +++ b/src/components/dialog/edit-widget.js @@ -41,7 +41,7 @@ class EditWidgetDialog extends Component { this.state = { temporal: { name: '', - simulator: '', + simulator: {}, signal: 0 } }; @@ -58,11 +58,25 @@ class EditWidgetDialog extends Component { handleChange(e) { if (e.constructor === Array) { // Every property in the array will be updated - let changes = e.reduce( (changesObject, event) => { changesObject[event.target.id] = event.target.value; return changesObject }, {}); + let changes = e.reduce( (changesObject, event) => { + if (event.target.id === 'simulator') { + changesObject[event.target.id] = JSON.parse(event.target.value); + } else { + changesObject[event.target.id] = event.target.value; + } + + return changesObject; + }, {}); + this.setState({ temporal: Object.assign({}, this.state.temporal, changes ) }); } else { let changeObject = {}; - changeObject[e.target.id] = e.target.value; + if (e.target.id === 'simulator') { + changeObject[e.target.id] = JSON.parse(e.target.value); + } else { + changeObject[e.target.id] = e.target.value; + } + this.setState({ temporal: Object.assign({}, this.state.temporal, changeObject ) }); } } @@ -87,7 +101,7 @@ class EditWidgetDialog extends Component { } render() { - + let controls = null; if (this.props.widget) { controls = createControls( diff --git a/src/components/dialog/new-node.js b/src/components/dialog/new-node.js new file mode 100644 index 0000000..44afa9d --- /dev/null +++ b/src/components/dialog/new-node.js @@ -0,0 +1,97 @@ +/** + * File: new-node.js + * Author: Markus Grigull + * Date: 06.07.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 . + ******************************************************************************/ + +import React from 'react'; +import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; + +import Dialog from './dialog'; + +class NewNodeDialog extends React.Component { + valid: false; + + constructor(props) { + super(props); + + this.state = { + name: '', + endpoint: '', + config: {}, + simulators: [] + }; + } + + onClose(canceled) { + if (canceled === false) { + this.props.onClose(this.state); + } else { + this.props.onClose(); + } + } + + handleChange(e) { + this.setState({ [e.target.id]: e.target.value }); + } + + resetState() { + this.setState({ name: '', endpoint: '', config: {}, simulators: [] }); + } + + validateForm(target) { + // check all controls + var endpoint = true; + var name = true; + + if (this.state.name === '' || this.props.nodes.find(node => node.name === this.state.name) !== undefined) { + name = false; + } + + if (this.state.endpoint === '' || this.props.nodes.find(node => node.endpoint === this.state.endpoint) !== undefined) { + endpoint = false; + } + + this.valid = endpoint && name; + + // return state to control + if (target === 'name') return name ? "success" : "error"; + else return endpoint ? "success" : "error"; + } + + render() { + return ( + this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> +
+ + Name + this.handleChange(e)} /> + + + + Endpoint + this.handleChange(e)} /> + + +
+
+ ); + } +} + +export default NewNodeDialog; diff --git a/src/components/dialog/new-simulation-model.js b/src/components/dialog/new-simulation-model.js index 5789e51..a0af323 100644 --- a/src/components/dialog/new-simulation-model.js +++ b/src/components/dialog/new-simulation-model.js @@ -30,7 +30,7 @@ class NewSimulationModelDialog extends Component { static propTypes = { show: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, - simulators: PropTypes.array.isRequired + nodes: PropTypes.array.isRequired }; valid: false; @@ -40,7 +40,7 @@ class NewSimulationModelDialog extends Component { this.state = { name: '', - simulator: '', + simulator: { node: '', simulator: '' }, length: '1', mapping: [ { name: 'Signal', type: 'Type' } ] }; @@ -68,7 +68,11 @@ class NewSimulationModelDialog extends Component { } } - this.setState({ [e.target.id]: e.target.value }); + if (e.target.id === 'simulator') { + this.setState({ simulator: JSON.parse(e.target.value) }); + } else { + this.setState({ [e.target.id]: e.target.value }); + } } handleMappingChange(event, row, column) { @@ -86,7 +90,7 @@ class NewSimulationModelDialog extends Component { resetState() { this.setState({ name: '', - simulator: this.props.simulators[0] != null ? this.props.simulators[0]._id : '', + simulator: { node: this.props.nodes[0] ? this.props.nodes[0]._id : '', simulator: this.props.nodes[0].simulators[0] ? 0 : '' }, length: '1', mapping: [ { name: 'Signal', type: 'Type' } ] }); @@ -130,9 +134,11 @@ class NewSimulationModelDialog extends Component {
Simulator - this.handleChange(e)}> - {this.props.simulators.map(simulator => ( - + this.handleChange(e)}> + {this.props.nodes.map(node => ( + node.simulators.map((simulator, index) => ( + + )) ))} diff --git a/src/components/dialog/new-simulator.js b/src/components/dialog/new-simulator.js index 395cf9e..f4c293d 100644 --- a/src/components/dialog/new-simulator.js +++ b/src/components/dialog/new-simulator.js @@ -36,8 +36,7 @@ class NewSimulatorDialog extends Component { super(props); this.state = { - name: '', - endpoint: '' + name: '' }; } @@ -54,27 +53,21 @@ class NewSimulatorDialog extends Component { } resetState() { - this.setState({ name: '', endpoint: '' }); + this.setState({ name: '' }); } validateForm(target) { // check all controls - var endpoint = true; var name = true; - if (this.state.name === '') { + if (this.state.name === '' || this.props.node.simulators.find(simulator => simulator.name === this.state.name) !== undefined) { name = false; } - if (this.state.endpoint === '') { - endpoint = false; - } - - this.valid = endpoint && name; + this.valid = name; // return state to control if (target === 'name') return name ? "success" : "error"; - else return endpoint ? "success" : "error"; } render() { @@ -86,11 +79,6 @@ class NewSimulatorDialog extends Component { this.handleChange(e)} />
- - Endpoint - this.handleChange(e)} /> - - ); diff --git a/src/components/node-tree.js b/src/components/node-tree.js new file mode 100644 index 0000000..24c1be3 --- /dev/null +++ b/src/components/node-tree.js @@ -0,0 +1,125 @@ +/** + * File: node-tree.js + * Author: Markus Grigull + * Date: 26.06.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 . + ******************************************************************************/ + +import React from 'react'; +import SortableTree from 'react-sortable-tree'; +import { Button, Glyphicon } from 'react-bootstrap'; + +class NodeTree extends React.Component { + constructor(props) { + super(props); + + this.state = { + treeData: [] + }; + } + + canNodeDrag(node, path) { + return (node.parentNode != null); + } + + canNodeDrop(node, prevPath) { + return (node.nextParent != null); + } + + generateNodeProps(rowInfo) { + var buttons = []; + + if (rowInfo.parentNode == null) { + buttons.push(); + buttons.push(); + buttons.push(); + } else { + // get child index + var index = rowInfo.path[1] - rowInfo.path[0] - 1; + + buttons.push(); + buttons.push(); + } + + return { + buttons: buttons + }; + } + + nodesToTreeData(nodes) { + var treeData = []; + + nodes.forEach((node) => { + var parent = { title: node.name, subtitle: node.endpoint, id: node._id, config: node.config, children: [], expanded: true }; + + node.simulators.forEach((simulator) => { + parent.children.push({ title: simulator.name, subtitle: simulator.id != null ? 'Online' : 'Offline' }); + }); + + treeData.push(parent); + }); + + return treeData; + } + + treeDataToNodes(treeData) { + var nodes = []; + + treeData.forEach((data) => { + var node = { name: data.title, endpoint: data.subtitle, _id: data.id, config: data.config, simulators: [] }; + + data.children.forEach((child) => { + node.simulators.push({ name: child.title }); + }); + + nodes.push(node); + }); + + return nodes; + } + + componentWillReceiveProps(nextProps) { + // compare if data changed + if (this.props.data == null || this.props.data !== nextProps.data) { + // generate new state + var treeData = this.nodesToTreeData(nextProps.data); + this.setState({ treeData }); + } + } + + onDataChange(treeData) { + this.setState({ treeData }); + + this.props.onDataChange(this.treeDataToNodes(treeData)) + } + + render() { + return ( + this.onDataChange(treeData)} + style={{ height: 400 }} + maxDepth={ 2 } + canDrag={(node, path) => this.canNodeDrag(node, path)} + canDrop={(node, prevPath) => this.canNodeDrop(node, prevPath)} + generateNodeProps={(rowInfo) => this.generateNodeProps(rowInfo)} + /> + ); + } +} + +export default NodeTree; diff --git a/src/components/widget-gauge.js b/src/components/widget-gauge.js index b4365fe..800f043 100644 --- a/src/components/widget-gauge.js +++ b/src/components/widget-gauge.js @@ -69,25 +69,27 @@ class WidgetGauge extends Component { return this.state.value !== nextState.value; } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps) { // update value const simulator = nextProps.widget.simulator; - if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].values == null) { + if (nextProps.data == null || nextProps.data[simulator.node][simulator.simulator] == null || nextProps.data[simulator.node][simulator.simulator].values == null) { this.setState({ value: 0 }); return; } // check if value has changed - const signal = nextProps.data[simulator].values[nextProps.widget.signal]; + const signal = nextProps.data[simulator.node][simulator.simulator].values[nextProps.widget.signal]; // Take just 3 decimal positions // Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String - const new_value = Math.round( signal[signal.length - 1].y * 1e3 ) / 1e3; - if (this.state.value !== new_value) { - this.setState({ value: new_value }); - - // update gauge's value - this.gauge.set(new_value); + if (signal != null) { + const new_value = Math.round( signal[signal.length - 1].y * 1e3 ) / 1e3; + if (this.state.value !== new_value) { + this.setState({ value: new_value }); + + // update gauge's value + this.gauge.set(new_value); + } } } @@ -101,8 +103,8 @@ class WidgetGauge extends Component { var signalType = null; if (this.props.simulation) { - var simulationModel = this.props.simulation.models.filter((model) => model.simulator === this.props.widget.simulator)[0]; - signalType = simulationModel && simulationModel.length > 0? simulationModel.mapping[this.props.widget.signal].type : ''; + var simulationModel = this.props.simulation.models.filter((model) => model.simulator.node === this.props.widget.simulator.node && model.simulator.simulator === this.props.widget.simulator.simulator)[0]; + signalType = (simulationModel != null && simulationModel.length > 0) ? simulationModel.mapping[this.props.widget.signal].type : ''; } return ( diff --git a/src/components/widget-plot-table.js b/src/components/widget-plot-table.js index 0507b3f..7862a05 100644 --- a/src/components/widget-plot-table.js +++ b/src/components/widget-plot-table.js @@ -45,7 +45,7 @@ class WidgetPlotTable extends Component { // Identify if there was a change in the preselected signals if (nextProps.simulation && (JSON.stringify(nextProps.widget.preselectedSignals) !== JSON.stringify(this.props.widget.preselectedSignals) || this.state.preselectedSignals.length === 0)) { - + // Update the currently selected signals by intersecting with the preselected signals // Do the same with the plot values var intersection = this.computeIntersection(nextProps.widget.preselectedSignals, nextProps.widget.signals); @@ -54,20 +54,19 @@ class WidgetPlotTable extends Component { this.updatePreselectedSignalsState(nextProps); return; } - } // Perform the intersection of the lists, alternatively could be done with Sets ensuring unique values computeIntersection(preselectedSignals, selectedSignals) { return preselectedSignals.filter( s => selectedSignals.includes(s)); } - + updatePreselectedSignalsState(nextProps) { const simulator = nextProps.widget.simulator; // get simulation model const simulationModel = nextProps.simulation.models.find((model) => { - return (model.simulator === simulator); + return (model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator); }); let preselectedSignals = []; @@ -89,13 +88,13 @@ class WidgetPlotTable extends Component { return accum; }, []); } - + this.setState({ preselectedSignals: preselectedSignals }); } updateSignalSelection(signal_index, checked) { // Update the selected signals and propagate to parent component - var new_widget = Object.assign({}, this.props.widget, { + var new_widget = Object.assign({}, this.props.widget, { signals: checked? this.state.signals.concat(signal_index) : this.state.signals.filter( (idx) => idx !== signal_index ) }); this.props.onWidgetChange(new_widget); @@ -106,31 +105,37 @@ class WidgetPlotTable extends Component { // Data passed to plot let simulator = this.props.widget.simulator; - let simulatorData = this.props.data[simulator]; + let simulatorData = []; + + if (this.props.data[simulator.node] != null && this.props.data[simulator.node][simulator.simulator] != null) { + simulatorData = this.props.data[simulator.node][simulator.simulator]; + } if (this.state.preselectedSignals && this.state.preselectedSignals.length > 0) { // Create checkboxes using the signal indices from simulation model checkBoxes = this.state.preselectedSignals.map( (signal) => { - var checked = this.state.signals.indexOf(signal.index) > -1; - var chkBxClasses = classNames({ - 'btn': true, - 'btn-default': true, - 'active': checked - }); - return this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } - }); + var checked = this.state.signals.indexOf(signal.index) > -1; + var chkBxClasses = classNames({ + 'btn': true, + 'btn-default': true, + 'active': checked + }); + return this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } + }); } // Prepare an array with the signals to show in the legend var legendSignals = this.state.preselectedSignals.reduce( (accum, signal, i) => { - if (this.state.signals.includes(signal.index)) { - accum.push({ - index: signal.index, - name: signal.name - }) - } - return accum; - }, []); + if (this.state.signals.includes(signal.index)) { + accum.push({ + index: signal.index, + name: signal.name + }); + } + return accum; + }, []); + + return (
diff --git a/src/components/widget-plot.js b/src/components/widget-plot.js index ff0b1b1..c02aa97 100644 --- a/src/components/widget-plot.js +++ b/src/components/widget-plot.js @@ -27,7 +27,6 @@ import PlotLegend from './widget-plot/plot-legend'; class WidgetPlot extends Component { render() { - const simulator = this.props.widget.simulator; const simulation = this.props.simulation; let legendSignals = []; @@ -36,10 +35,10 @@ class WidgetPlot extends Component { // Proceed if a simulation with models and a simulator are available if (simulator && simulation && simulation.models.length > 0) { - const model = simulation.models.find( (model) => model.simulator === simulator ); + const model = simulation.models.find( model => model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator ); const chosenSignals = this.props.widget.signals; - simulatorData = this.props.data[simulator]; + simulatorData = this.props.data[simulator.node][simulator.simulator]; // Query the signals that will be displayed in the legend legendSignals = model.mapping.reduce( (accum, model_signal, signal_index) => { @@ -53,7 +52,7 @@ class WidgetPlot extends Component { return (

{this.props.widget.name}

- +
diff --git a/src/components/widget-plot/plot.js b/src/components/widget-plot/plot.js index 6bae9d0..7b0c3aa 100644 --- a/src/components/widget-plot/plot.js +++ b/src/components/widget-plot/plot.js @@ -14,7 +14,7 @@ import { scaleOrdinal, schemeCategory10 } from 'd3-scale'; class Plot extends Component { constructor(props) { super(props); - + this.chartWrapper = null; // Initialize plot size and data @@ -24,7 +24,7 @@ class Plot extends Component { ); } - // Get an object with 'invisible' init data for the last minute. + // Get an object with 'invisible' init data for the last minute. // Include start/end timestamps if required. getPlotInitData(withRangeTimestamps = false) { @@ -32,8 +32,8 @@ class Plot extends Component { const initFirstTime = initSecondTime - 1000 * 60; // Decrease 1 min const values = [{ values: [{x: initFirstTime, y: 0}], strokeWidth: 0 }]; - let output = withRangeTimestamps? - { sequence: 0, values: values, firstTimestamp: initFirstTime, latestTimestamp: initSecondTime, } : + let output = withRangeTimestamps? + { sequence: 0, values: values, firstTimestamp: initFirstTime, latestTimestamp: initSecondTime, } : { sequence: 0, values: values }; return output; @@ -58,19 +58,19 @@ class Plot extends Component { // Identify simulation reset if (nextData == null || nextData.length === 0 || nextData.values[0].length === 0) { this.clearPlot(); return; } - + // check if new data, otherwise skip if (this.state.sequence >= nextData.sequence) { return; } - + this.updatePlotData(nextProps); } signalsWereJustCleared(nextProps) { - return this.props.signals && - nextProps.signals && - this.props.signals.length > 0 && + return this.props.signals && + nextProps.signals && + this.props.signals.length > 0 && nextProps.signals.length === 0; } @@ -102,7 +102,7 @@ class Plot extends Component { nextProps.signals.forEach((signal_index, i, arr) => ( // Include signal index, useful to relate them to the signal selection values.push( - { + { index: signal_index, values: nextData.values[signal_index].slice(firstIndex, nextData.values[signal_index].length - 1)}) )); @@ -138,4 +138,4 @@ class Plot extends Component { } -export default Plot; \ No newline at end of file +export default Plot; diff --git a/src/components/widget-table.js b/src/components/widget-table.js index 9c48a00..44d3966 100644 --- a/src/components/widget-table.js +++ b/src/components/widget-table.js @@ -38,33 +38,33 @@ class WidgetTable extends Component { // check data const simulator = nextProps.widget.simulator; - if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].length === 0 || nextProps.data[simulator].values[0].length === 0) { + if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator.node][simulator.simulator] == null || nextProps.data[simulator.node][simulator.simulator].length === 0 || nextProps.data[simulator.node][simulator.simulator].values.length === 0 || nextProps.data[simulator.node][simulator.simulator].values[0].length === 0) { // clear values this.setState({ rows: [], sequence: null }); return; } // check if new data, otherwise skip - if (this.state.sequence >= nextProps.data[simulator].sequence) { + /*if (this.state.sequence >= nextProps.data[simulator.node][simulator.simulator].sequence) { return; - } + }*/ // get simulation model const simulationModel = nextProps.simulation.models.find((model) => { - return (model.simulator === simulator); + return (model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator); }); // get rows var rows = []; - nextProps.data[simulator].values.forEach((signal, index) => { + nextProps.data[simulator.node][simulator.simulator].values.forEach((signal, index) => { rows.push({ name: simulationModel.mapping[index].name, value: signal[signal.length - 1].y.toFixed(3) }) }); - this.setState({ rows: rows, sequence: nextProps.data[simulator].sequence }); + this.setState({ rows: rows, sequence: nextProps.data[simulator.node][simulator.simulator].sequence }); } render() { diff --git a/src/components/widget-value.js b/src/components/widget-value.js index eeceeed..fdbb04b 100644 --- a/src/components/widget-value.js +++ b/src/components/widget-value.js @@ -32,16 +32,19 @@ class WidgetValue extends Component { componentWillReceiveProps(nextProps) { // update value - const simulator = nextProps.widget.simulator; + const simulator = nextProps.widget.simulator.simulator; + const node = nextProps.widget.simulator.node; - if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].values == null) { + //console.log(nextProps.widget.simulator); + + if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].values == null) { this.setState({ value: '' }); return; } // check if value has changed - const signal = nextProps.data[simulator].values[nextProps.widget.signal]; - if (this.state.value !== signal[signal.length - 1].y) { + const signal = nextProps.data[node][simulator].values[nextProps.widget.signal]; + if (signal != null && this.state.value !== signal[signal.length - 1].y) { this.setState({ value: signal[signal.length - 1].y }); } } diff --git a/src/containers/app.js b/src/containers/app.js index 2af248e..70bbe67 100644 --- a/src/containers/app.js +++ b/src/containers/app.js @@ -27,7 +27,7 @@ import NotificationSystem from 'react-notification-system'; import AppDispatcher from '../app-dispatcher'; import SimulationStore from '../stores/simulation-store'; -import SimulatorStore from '../stores/simulator-store'; +import NodeStore from '../stores/node-store'; import UserStore from '../stores/user-store'; import NotificationsDataManager from '../data-managers/notifications-data-manager'; @@ -40,12 +40,12 @@ import '../styles/app.css'; class App extends Component { static getStores() { - return [ SimulationStore, SimulatorStore, UserStore ]; + return [ NodeStore, UserStore, SimulationStore ]; } static calculateState(prevState) { // get list of running simulators - var simulators = SimulatorStore.getState().filter(simulator => { + /*var simulators = SimulatorStore.getState().filter(simulator => { return simulator.running === true; }); @@ -74,16 +74,17 @@ class App extends Component { if (equal) { simulators = prevState.runningSimulators; } - } + }*/ let currentUser = UserStore.getState().currentUser; return { + nodes: NodeStore.getState(), simulations: SimulationStore.getState(), currentRole: currentUser? currentUser.role : '', - token: UserStore.getState().token, + token: UserStore.getState().token/*, - runningSimulators: simulators + runningSimulators: simulators*/ }; } @@ -103,7 +104,7 @@ class App extends Component { // load all simulators and simulations to fetch simulation data AppDispatcher.dispatch({ - type: 'simulators/start-load' + type: 'nodes/start-load' }); AppDispatcher.dispatch({ @@ -126,52 +127,47 @@ class App extends Component { return; } - // open connection to each required simulator - const requiredSimulators = this.requiredSimulatorsBySimulations(); + // open connection to each node + /*const requiredNodes = this.requiredNodesBySimulations(); - requiredSimulators.forEach(simulator => { - this.connectSimulator(nextState, simulator); - }); + requiredNodes.forEach(node => { + AppDispatcher.dispatch({ + type: 'simulatorData/open', + identifier: simulator._id, + endpoint: node.endpoint, + signals: data.signals + }); + });*/ } - requiredSimulatorsBySimulations() { - var simulators = []; + /*requiredNodesBySimulations() { + var nodes = {}; - this.state.simulations.forEach((simulation) => { - simulation.models.forEach((simulationModel) => { - // add simulator to list if not already part of - const index = simulators.findIndex((element) => { - return element.simulator === simulationModel.simulator; + this.state.simulations.forEach(simulation => { + simulation.models.forEach(model => { + // get ID for node + var node = this.state.nodes.find(element => { + return element.name === model.simulator.node; }); - if (index === -1) { - simulators.push({ simulator: simulationModel.simulator, signals: simulationModel.length }); - } else { - if (simulators[index].length < simulationModel.length) { - simulators[index].length = simulationModel.length; + // add empty node if not existing + if (node !== undefined) { + if (nodes[node._id] == null) { + nodes[node._id] = { simulators: [] } } + + // get simulator id + var simulator = node.simulators.find(simulator => { + return simulator.name === model.simulator.simulator; + }); + + nodes[node._id].simulators.push({ id: simulator.id, signals: model.length }); } }); }); - return simulators; - } - - connectSimulator(state, data) { - // get simulator object - const simulator = state.runningSimulators.find(element => { - return element._id === data.simulator; - }); - - if (simulator != null) { - AppDispatcher.dispatch({ - type: 'simulatorData/open', - identifier: simulator._id, - endpoint: simulator.endpoint, - signals: data.signals - }); - } - } + return nodes; + }*/ render() { // get children diff --git a/src/containers/simulation.js b/src/containers/simulation.js index 59e37c5..0aff4bf 100644 --- a/src/containers/simulation.js +++ b/src/containers/simulation.js @@ -24,7 +24,7 @@ import { Container } from 'flux/utils'; import { Button, Modal, Glyphicon } from 'react-bootstrap'; import SimulationStore from '../stores/simulation-store'; -import SimulatorStore from '../stores/simulator-store'; +import NodeStore from '../stores/node-store'; import AppDispatcher from '../app-dispatcher'; import Table from '../components/table'; @@ -34,13 +34,13 @@ import EditSimulationModelDialog from '../components/dialog/edit-simulation-mode class Simulation extends Component { static getStores() { - return [ SimulationStore, SimulatorStore ]; + return [ SimulationStore, NodeStore ]; } static calculateState() { return { simulations: SimulationStore.getState(), - simulators: SimulatorStore.getState(), + nodes: NodeStore.getState(), newModal: false, deleteModal: false, @@ -58,7 +58,7 @@ class Simulation extends Component { }); AppDispatcher.dispatch({ - type: 'simulators/start-load' + type: 'nodes/start-load' }); } @@ -119,14 +119,16 @@ class Simulation extends Component { } } - getSimulatorName(id) { - for (var i = 0; i < this.state.simulators.length; i++) { - if (this.state.simulators[i]._id === id) { - return this.state.simulators[i].name; - } - } + getSimulatorName(simulator) { + var name = "undefined"; - return id; + this.state.nodes.forEach(node => { + if (node._id === simulator.node) { + name = node.name + '/' + node.simulators[simulator.simulator].name; + } + }); + + return name; } render() { @@ -136,16 +138,16 @@ class Simulation extends Component { - this.getSimulatorName(id)} /> + this.getSimulatorName(simulator)} /> this.setState({ editModal: true, modalData: this.state.simulation.models[index], modalIndex: index })} onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulation.models[index], modalIndex: index })} />
- this.closeNewModal(data)} simulators={this.state.simulators} /> + this.closeNewModal(data)} nodes={this.state.nodes} /> - this.closeEditModal(data)} data={this.state.modalData} simulators={this.state.simulators} /> + this.closeEditModal(data)} data={this.state.modalData} nodes={this.state.nodes} /> diff --git a/src/containers/simulators.js b/src/containers/simulators.js index e9a2af8..3e258a6 100644 --- a/src/containers/simulators.js +++ b/src/containers/simulators.js @@ -24,74 +24,168 @@ import { Container } from 'flux/utils'; import { Button, Modal, Glyphicon } from 'react-bootstrap'; import AppDispatcher from '../app-dispatcher'; -import SimulatorStore from '../stores/simulator-store'; +import NodeStore from '../stores/node-store'; -import Table from '../components/table'; -import TableColumn from '../components/table-column'; +import NewNodeDialog from '../components/dialog/new-node'; +import EditNodeDialog from '../components/dialog/edit-node'; import NewSimulatorDialog from '../components/dialog/new-simulator'; import EditSimulatorDialog from '../components/dialog/edit-simulator'; +import NodeTree from '../components/node-tree'; class Simulators extends Component { static getStores() { - return [ SimulatorStore ]; + return [ NodeStore ]; } static calculateState() { return { - simulators: SimulatorStore.getState(), + nodes: NodeStore.getState(), - newModal: false, - deleteModal: false, - editModal: false, - modalSimulator: {} + newNodeModal: false, + deleteNodeModal: false, + editNodeModal: false, + + addSimulatorModal: false, + editSimulatorModal: false, + deleteSimulatorModal: false, + + modalData: {}, + modalIndex: 0, + modalName: '' }; } componentWillMount() { AppDispatcher.dispatch({ - type: 'simulators/start-load' + type: 'nodes/start-load' }); } - closeNewModal(data) { - this.setState({ newModal : false }); + closeNewNodeModal(data) { + this.setState({ newNodeModal: false }); if (data) { AppDispatcher.dispatch({ - type: 'simulators/start-add', + type: 'nodes/start-add', data: data }); } } - confirmDeleteModal() { + showEditNodeModal(data) { + // find node with id + var node = this.state.nodes.find((element) => { + return element._id === data.id; + }); + + this.setState({ editNodeModal: true, modalData: node }); + } + + closeEditNodeModal(data) { + this.setState({ editNodeModal: false }); + + if (data) { + AppDispatcher.dispatch({ + type: 'nodes/start-edit', + data: data + }); + } + } + + showDeleteNodeModal(data) { + // find node with id + var node = this.state.nodes.find((element) => { + return element._id === data.id; + }); + + this.setState({ deleteNodeModal: true, modalData: node }); + } + + confirmDeleteNodeModal() { this.setState({ deleteModal: false }); AppDispatcher.dispatch({ - type: 'simulators/start-remove', - data: this.state.modalSimulator + type: 'nodes/start-remove', + data: this.state.modalData }); } - closeEditModal(data) { - this.setState({ editModal : false }); + showAddSimulatorModal(data) { + // find node with id + var node = this.state.nodes.find((element) => { + return element._id === data.id; + }); + + this.setState({ addSimulatorModal: true, modalData: node }); + } + + closeAddSimulatorModal(data) { + this.setState({ addSimulatorModal: false }); if (data) { + var node = this.state.modalData; + node.simulators.push(data); + AppDispatcher.dispatch({ - type: 'simulators/start-edit', - data: data + type: 'nodes/start-edit', + data: node }); } } - labelStyle(value) { - if (value === true) return 'success'; - else return 'warning'; + showEditSimulatorModal(data, index) { + // find node with id + var node = this.state.nodes.find((element) => { + return element._id === data.id; + }); + + this.setState({ editSimulatorModal: true, modalData: node, modalIndex: index }); } - labelModifier(value) { - if (value === true) return 'Running'; - else return 'Not running'; + closeEditSimulatorModal(data) { + this.setState({ editSimulatorModal: false }); + + if (data) { + var node = this.state.modalData; + node.simulators[this.state.modalIndex] = data; + + AppDispatcher.dispatch({ + type: 'nodes/start-edit', + data: node + }); + } + } + + showDeleteSimulatorModal(data, index) { + // find node with id + var node = this.state.nodes.find((element) => { + return element._id === data.id; + }); + + this.setState({ deleteSimulatorModal: true, modalData: node, modalIndex: index, modalName: data.children[index].title }); + } + + confirmDeleteSimulatorModal() { + this.setState({ deleteSimulatorModal: false }); + + // remove simulator + var node = this.state.modalData; + node.simulators.splice(this.state.modalIndex); + + AppDispatcher.dispatch({ + type: 'nodes/start-edit', + data: node + }); + } + + onTreeDataChange(nodes) { + // update all at once + nodes.forEach((node) => { + AppDispatcher.dispatch({ + type: 'nodes/start-edit', + data: node + }); + }); } render() { @@ -99,30 +193,50 @@ class Simulators extends Component {

Simulators

- - this.labelStyle(value)} labelModifier={(value) => this.labelModifier(value)} /> - - this.setState({ editModal: true, modalSimulator: this.state.simulators[index] })} onDelete={(index) => this.setState({ deleteModal: true, modalSimulator: this.state.simulators[index] })} /> -
+ - +
+ Hint: Node names must be unique. Simulator names must be unique on a node. - this.closeNewModal(data)} /> + this.onTreeDataChange(treeData)} onNodeDelete={(node) => this.showDeleteNodeModal(node)} onNodeEdit={(node) => this.showEditNodeModal(node)} onNodeAdd={(node) => this.showAddSimulatorModal(node)} onSimulatorEdit={(node, index) => this.showEditSimulatorModal(node, index)} onSimulatorDelete={(node, index) => this.showDeleteSimulatorModal(node, index)} /> - this.closeEditModal(data)} simulator={this.state.modalSimulator} /> + this.closeNewNodeModal(data)} nodes={this.state.nodes} /> + this.closeEditNodeModal(data)} nodes={this.state.nodes} /> + this.closeAddSimulatorModal(data)} node={this.state.modalData}/> - + {this.state.editSimulatorModal && + this.closeEditSimulatorModal(data)} node={this.state.modalData} /> + } + + + + Delete Node + + + + Are you sure you want to delete the node '{this.state.modalData.name}'? +
+ This will delete all simulators assigned to this node. +
+ + + + + +
+ + Delete Simulator - Are you sure you want to delete the simulator '{this.state.modalSimulator.name}'? + Are you sure you want to delete the simulator '{this.state.modalName}'? - - + +
diff --git a/src/containers/visualization.js b/src/containers/visualization.js index a526df3..425f394 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -65,13 +65,13 @@ class Visualization extends Component { editModal: prevState.editModal || false, modalData: prevState.modalData || null, modalIndex: prevState.modalIndex || null, - + maxWidgetHeight: prevState.maxWidgetHeight || 0, dropZoneHeight: prevState.dropZoneHeight || 0, last_widget_key: prevState.last_widget_key || 0 }; } - + componentWillMount() { AppDispatcher.dispatch({ type: 'visualizations/start-load' @@ -167,7 +167,7 @@ class Visualization extends Component { var visualization = Object.assign({}, this.state.visualization, { widgets: new_widgets }); - + this.increaseHeightWithWidget(widget); this.setState({ visualization: visualization }); } @@ -185,7 +185,7 @@ class Visualization extends Component { 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); @@ -201,12 +201,12 @@ class Visualization extends Component { 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, + this.setState({ + maxWidgetHeight: maxHeight, dropZoneHeight: maxHeight + 40 }); } @@ -219,8 +219,8 @@ class Visualization extends Component { let thisWidgetHeight = widget.y + widget.height; if (thisWidgetHeight > this.state.maxWidgetHeight) { increased = true; - this.setState({ - maxWidgetHeight: thisWidgetHeight, + this.setState({ + maxWidgetHeight: thisWidgetHeight, dropZoneHeight: thisWidgetHeight + 40 }); } @@ -235,7 +235,7 @@ class Visualization extends Component { if (data) { // save changes temporarily var widgets_update = {}; - widgets_update[this.state.modalIndex] = data; + widgets_update[this.state.modalIndex] = data; var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update); diff --git a/src/data-managers/nodes-data-manager.js b/src/data-managers/nodes-data-manager.js new file mode 100644 index 0000000..12e4014 --- /dev/null +++ b/src/data-managers/nodes-data-manager.js @@ -0,0 +1,69 @@ +/** + * File: nodes-data-manager.js + * Author: Markus Grigull + * Date: 26.06.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 . + ******************************************************************************/ + +import RestDataManager from './rest-data-manager'; +import RestAPI from '../api/rest-api'; +import AppDispatcher from '../app-dispatcher'; + +class NodesDataManager extends RestDataManager { + constructor() { + super('node', '/nodes'); + } + + getSimulators(node) { + RestAPI.post('http://' + node.endpoint + '/api/v1', { + action: 'nodes', + id: node._id + }).then(response => { + // assign IDs to simulators + response.response.forEach(element => { + if (element.type === "websocket") { + // add the (villas-node) node ID to the simulator + node.simulators = node.simulators.map(simulator => { + if (simulator.name === element.name) { + simulator.id = element.id; + } + + return simulator; + }); + } + }); + + AppDispatcher.dispatch({ + type: 'nodes/edited', + data: node + }); + + AppDispatcher.dispatch({ + type: 'simulatorData/open', + node: node, + endpoint: node.endpoint, + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'nodes/edit-error', + error: error + }); + }); + } +} + +export default new NodesDataManager(); diff --git a/src/data-managers/rest-data-manager.js b/src/data-managers/rest-data-manager.js index 51560a2..749cdcd 100644 --- a/src/data-managers/rest-data-manager.js +++ b/src/data-managers/rest-data-manager.js @@ -22,7 +22,7 @@ import RestAPI from '../api/rest-api'; import AppDispatcher from '../app-dispatcher'; -const HOST = process.env.REACT_APP_HTTP_PROXY || ""; +const HOST = process.env.REACT_APP_HTTP_PROXY || "http://localhost:4000"; const API_URL = HOST + '/api/v1'; class RestDataManager { diff --git a/src/data-managers/simulator-data-data-manager.js b/src/data-managers/simulator-data-data-manager.js index 22f443b..666ea5e 100644 --- a/src/data-managers/simulator-data-data-manager.js +++ b/src/data-managers/simulator-data-data-manager.js @@ -27,21 +27,21 @@ class SimulatorDataDataManager { this._sockets = {}; } - open(endpoint, identifier, signals) { + open(endpoint, node) { // pass signals to onOpen callback - if (this._sockets[identifier] != null) { - if (this._sockets[identifier].url !== WebsocketAPI.getURL(endpoint)) { + if (this._sockets[node._id] != null) { + if (this._sockets[node._id].url !== WebsocketAPI.getURL(endpoint)) { // replace connection, since endpoint changed this._sockets.close(); - this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + this._sockets[node._id] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, node), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node) }); } } else { // set flag if a socket to this simulator was already create before - if (this._sockets[identifier] === null) { - this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals, false), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + if (this._sockets[node._id] === null) { + this._sockets[node._id] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, node, false), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node) }); } else { - this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals, true), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) }); + this._sockets[node._id] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, node, true), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node) }); } } } @@ -56,33 +56,32 @@ class SimulatorDataDataManager { } } - onOpen(event, identifier, signals, firstOpen) { + onOpen(event, node, firstOpen) { AppDispatcher.dispatch({ type: 'simulatorData/opened', - identifier: identifier, - signals: signals, + node: node, firstOpen: firstOpen }); } - onClose(event, identifier) { + onClose(event, node) { AppDispatcher.dispatch({ type: 'simulatorData/closed', - identifier: identifier, + node: node, notification: (event.code !== 4000) }); // remove from list, keep null reference for flag detection - delete this._sockets[identifier]; + delete this._sockets[node._id]; } - onMessage(event, identifier) { + onMessage(event, node) { var message = this.bufferToMessage(event.data); AppDispatcher.dispatch({ type: 'simulatorData/data-changed', data: message, - identifier: identifier + node: node }); } @@ -95,6 +94,7 @@ class SimulatorDataDataManager { var bits = data.getUint8(0); var length = data.getUint16(0x02, 1); + var id = data.getUint8(1); var values = new Float32Array(data.buffer, data.byteOffset + 0x10, length); @@ -104,7 +104,8 @@ class SimulatorDataDataManager { length: length, sequence: data.getUint32(0x04, 1), timestamp: data.getUint32(0x08, 1) * 1e3 + data.getUint32(0x0C, 1) * 1e-6, - values: values + values: values, + id: id }; } } diff --git a/src/data-managers/simulators-data-manager.js b/src/data-managers/simulators-data-manager.js deleted file mode 100644 index 23dbe7b..0000000 --- a/src/data-managers/simulators-data-manager.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * File: simulators-data-manager.js - * Author: Markus Grigull - * 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 . - ******************************************************************************/ - -import RestDataManager from './rest-data-manager'; -import RestAPI from '../api/rest-api'; -import AppDispatcher from '../app-dispatcher'; - -function isRunning(simulator) { - // get path to nodes.json and simulator name - var path = simulator.endpoint.substring(0, simulator.endpoint.lastIndexOf('/')); - - var url = 'http://' + path + '/api/v1'; - var body = { - action: 'nodes', - id: '1234' /// @todo use random generated id - }; - - // send request - RestAPI.post(url, body).then(response => { - // check if simulator is running - simulator.running = false; - - if (response.id === body.id) { - response.response.forEach(sim => { - if (sim.name === simulator.name) { - simulator.running = true; - } - }); - } - - AppDispatcher.dispatch({ - type: 'simulators/running', - simulator: simulator, - running: simulator.running - }); - }).catch(error => { - simulator.running = false; - - AppDispatcher.dispatch({ - type: 'simulators/running', - simulator: simulator, - running: simulator.running - }); - }); -} - -class SimulatorsDataManager extends RestDataManager { - constructor() { - super('simulator', '/simulators', [ '_id', 'name', 'endpoint' ]); - - this._timers = []; - } - - startRunningDetection(obj) { - const simulator = JSON.parse(JSON.stringify(obj)); - - // check if timer is already running - const index = this._timers.findIndex(timer => { - return timer.simulator === simulator._id; - }); - - if (index !== -1) { - return; - } - - // do first request for fast response time - isRunning(simulator); - - // start new timer - const timerID = setInterval(isRunning, 5000, simulator); - this._timers.push({ id: timerID, simulator: simulator._id }); - } - - stopRunningDetection(simulator) { - // remove timer - const index = this._timers.findIndex(timer => { - return timer.simulator === simulator._id; - }); - - if (index !== -1) { - // stop timer and delete from list - clearInterval(this._timers[index].id); - this._timers.splice(index, 1); - } - } -} - -export default new SimulatorsDataManager(); diff --git a/src/stores/node-store.js b/src/stores/node-store.js new file mode 100644 index 0000000..aad1df6 --- /dev/null +++ b/src/stores/node-store.js @@ -0,0 +1,50 @@ +/** + * File: node-store.js + * Author: Markus Grigull + * Date: 26.06.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 . + ******************************************************************************/ + +import ArrayStore from './array-store'; +import NodesDataManager from '../data-managers/nodes-data-manager'; + +class NodeStore extends ArrayStore { + constructor() { + super('nodes', NodesDataManager); + } + + reduce(state, action) { + switch(action.type) { + case 'nodes/loaded': + // get simulator IDs + if (Array.isArray(action.data)) { + action.data.forEach(node => { + NodesDataManager.getSimulators(node); + }); + } else { + NodesDataManager.getSimulators(action.data); + } + + return super.reduce(state, action); + + default: + return super.reduce(state, action); + } + } +} + +export default new NodeStore(); diff --git a/src/stores/simulator-data-store.js b/src/stores/simulator-data-store.js index d52ac1d..2dd4336 100644 --- a/src/stores/simulator-data-store.js +++ b/src/stores/simulator-data-store.js @@ -40,48 +40,57 @@ class SimulationDataStore extends ReduceStore { switch (action.type) { case 'simulatorData/open': - SimulatorDataDataManager.open(action.endpoint, action.identifier, action.signals); + SimulatorDataDataManager.open(action.endpoint, action.node); return state; case 'simulatorData/opened': // create entry for simulator - state[action.identifier] = { signals: action.signals, values: [], sequence: null, timestamp: null }; + state[action.node._id] = {}; - for (i = 0; i < action.signals; i++) { - state[action.identifier].values.push([]); - } + action.node.simulators.forEach(simulator => { + state[action.node._id][simulator.id] = { sequence: -1, values: [] }; + }); return state; case 'simulatorData/data-changed': + // check if data is required, otherwise discard + if (state[action.node._id] == null || state[action.node._id][action.data.id] == null) { + return state; + } + // only add data, if newer than current - if (state[action.identifier].sequence < action.data.sequence) { + if (state[action.node._id][action.data.id].sequence < action.data.sequence) { // add data to simulator - for (i = 0; i < state[action.identifier].signals; i++) { - state[action.identifier].values[i].push({ x: action.data.timestamp, y: action.data.values[i] }); + for (i = 0; i < action.data.length; i++) { + while (state[action.node._id][action.data.id].values.length < i + 1) { + state[action.node._id][action.data.id].values.push([]); + } + + state[action.node._id][action.data.id].values[i].push({ x: action.data.timestamp, y: action.data.values[i] }); // erase old values - if (state[action.identifier].values[i].length > MAX_VALUES) { - const pos = state[action.identifier].values[i].length - MAX_VALUES; - state[action.identifier].values[i].splice(0, pos); + if (state[action.node._id][action.data.id].values[i].length > MAX_VALUES) { + const pos = state[action.node._id][action.data.id].values[i].length - MAX_VALUES; + state[action.node._id][action.data.id].values[i].splice(0, pos); } } // update metadata - state[action.identifier].timestamp = action.data.timestamp; - state[action.identifier].sequence = action.data.sequence; + state[action.node._id][action.data.id].timestamp = action.data.timestamp; + state[action.node._id][action.data.id].sequence = action.data.sequence; // explicit call to prevent array copy this.__emitChange(); } else { - console.log('same sequence ' + state[action.identifier].sequence + ' ' + action.data.sequence); + console.log('same sequence ' + state[action.node._id][action.data.id].sequence + ' ' + action.data.sequence); } return state; case 'simulatorData/closed': // close and delete socket - if (state[action.identifier] != null) { + if (state[action.node] != null) { // delete data //delete state[action.identifier]; //state[action.identifier] = null; diff --git a/src/stores/simulator-store.js b/src/stores/simulator-store.js deleted file mode 100644 index 4f8d6bd..0000000 --- a/src/stores/simulator-store.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * File: villas-store.js - * Author: Markus Grigull - * 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 . - ******************************************************************************/ - -import ArrayStore from './array-store'; -import SimulatorsDataManager from '../data-managers/simulators-data-manager'; -import NotificationsDataManager from '../data-managers/notifications-data-manager'; - -class SimulatorStore extends ArrayStore { - constructor() { - super('simulators', SimulatorsDataManager); - } - - reduce(state, action) { - var simulator; - - switch (action.type) { - - case 'simulators/added': - SimulatorsDataManager.startRunningDetection(action.data); - - return super.reduce(state, action); - - case 'simulators/removed': - SimulatorsDataManager.stopRunningDetection(action.original); - - return super.reduce(state, action); - - case 'simulators/start-edit': - // An update will be requested, stop the 'runningDetection' already - SimulatorsDataManager.stopRunningDetection(action.data); - - return super.reduce(state, action); - - case 'simulators/edited': - // The update was done, resume the 'runningDetection' - SimulatorsDataManager.startRunningDetection(action.data); - - return super.reduce(state, action); - - case 'simulators/loaded': - // get simulator running state - if (Array.isArray(action.data)) { - action.data.forEach((simulator) => { - SimulatorsDataManager.startRunningDetection(simulator); - }); - } else { - SimulatorsDataManager.startRunningDetection(action.data); - } - - return super.reduce(state, action); - - case 'simulators/running': - // check if simulator running state changed - simulator = state.find(element => element._id === action.simulator._id ); - - // is this simulator still in the state? update it only if state changed - if (simulator && simulator.running !== action.simulator.running) { - state = this.updateElements(state, [ action.simulator ]); - } - - return state; - - case 'simulatorData/opened': - // get simulator - simulator = state.find(element => { - return element._id === action.identifier; - }); - - if (action.firstOpen === false) { - NotificationsDataManager.addNotification({ - title: 'Simulator online', - message: 'Simulator \'' + simulator.name + '\' went online.', - level: 'info' - }); - } - - // restart requesting again - SimulatorsDataManager.stopRunningDetection(simulator); - - return state; - - case 'simulatorData/closed': - // get simulator - simulator = state.find(element => { - return element._id === action.identifier; - }); - - // update running state - simulator.running = false; - - if (action.notification) { - NotificationsDataManager.addNotification({ - title: 'Simulator offline', - message: 'Simulator \'' + simulator.name + '\' went offline.', - level: 'info' - }); - - // restart requesting again - SimulatorsDataManager.startRunningDetection(simulator); - } - - return this.updateElements(state, [ simulator ]); - - default: - return super.reduce(state, action); - } - } -} - -export default new SimulatorStore();