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 'feature_exportfunctions' into develop

This commit is contained in:
Sonja Happ 2020-05-29 11:37:50 +02:00
commit ff853b4d38
13 changed files with 399 additions and 175 deletions

View file

@ -99,7 +99,7 @@ class RestDataManager {
});
if (this.onLoad != null) {
this.onLoad(data);
this.onLoad(data, token);
}
}).catch(error => {
AppDispatcher.dispatch({
@ -121,7 +121,7 @@ class RestDataManager {
});
if (this.onLoad != null) {
this.onLoad(data);
this.onLoad(data, token);
}
}).catch(error => {
AppDispatcher.dispatch({
@ -133,16 +133,50 @@ class RestDataManager {
}
add(object, token = null, param = null) {
add(object, token = null, param = null, subObjects = null) {
var obj = {};
obj[this.type] = this.filterKeys(object);
RestAPI.post(this.requestURL('load/add',null,param), obj, token).then(response => {
AppDispatcher.dispatch({
type: this.type + 's/added',
data: response[this.type],
token: token
});
// check if POST is done for import of object and issue dispatches of sub-objects
if (subObjects !== null){
// there are sub-objects to be added for an import
for (let objectType of subObjects){
let type = Object.keys(objectType) // type can be dashboards, configs, widgets, ...
type = type[0];
for (let newObj of objectType[type]){
// set the ID of the object that the sub-object shall be associated with
if(type === "configs" || type === "dashboards"){
// the main object is a scenario
newObj.scenarioID = response[this.type].id
} else if (type === "widgets") {
// the main object is a dashboard
newObj.dashboardID = response[this.type].id
} else if (type === "signals") {
// the main object is a component configuration
newObj.configID = response[this.type].id
}
// iterate over all objects of type 'type' add issue add dispatch
AppDispatcher.dispatch({
type: type + '/start-add',
data: newObj,
token: token
})
}
}
}
}).catch(error => {
AppDispatcher.dispatch({
type: this.type + 's/add-error',

View file

@ -29,10 +29,38 @@ class ConfigStore extends ArrayStore {
case 'configs/loaded':
ConfigsDataManager.loadSignals(action.token, action.data);
ConfigsDataManager.loadFiles(action.token, action.data);
return super.reduce(state, action);
case 'configs/start-add':
// Check if this is a recursive component config import or not
if (action.data.hasOwnProperty("outputMapping") || action.data.hasOwnProperty("inputMapping")) {
// import
let subObjects = []
let outputMapping = {}
let inputMapping = {}
if (action.data.hasOwnProperty("outputMapping")){
outputMapping["signals"] = action.data.outputMapping
subObjects.push(outputMapping)
delete action.data.outputMapping; // remove outputMapping signals from config object
}
if (action.data.hasOwnProperty("inputMapping")){
inputMapping["signals"] = action.data.inputMapping
subObjects.push(inputMapping)
delete action.data.inputMapping; // remove inputMapping signals from config object
}
// action.data should now contain the config and no sub-objects
// sub-objects are treated in add method of RestDataManager
this.dataManager.add(action.data, action.token,action.param, subObjects);
return state
} else {
// no import
return super.reduce(state, action);
}
default:
return super.reduce(state, action);

View file

@ -26,43 +26,35 @@ class ConfigDataManager extends RestDataManager {
this.onLoad = this.onConfigsLoad;
}
onConfigsLoad(data) {
onConfigsLoad(data, token) {
if (!Array.isArray(data))
data = [ data ];
for (let config of data)
this.loadICData(config);
}
for (let config of data) {
loadICData(config) {
AppDispatcher.dispatch({
type: 'icData/prepare',
inputLength: parseInt(config.inputLength, 10),
outputLength: parseInt(config.outputLength, 10),
id: config.icID
});
}
// prepare IC data
AppDispatcher.dispatch({
type: 'icData/prepare',
inputLength: parseInt(config.inputLength, 10),
outputLength: parseInt(config.outputLength, 10),
id: config.icID
});
loadSignals(token, configs){
for (let config of configs) {
// request in signals
RestAPI.get(this.makeURL('/signals?direction=in&configID=' + config.id), token).then(response => {
AppDispatcher.dispatch({
type: 'signals/loaded',
data: response.signals
});
AppDispatcher.dispatch({
type: 'signals/start-load',
token: token,
param: '?direction=in&configID=' + config.id,
});
// request out signals
RestAPI.get(this.makeURL('/signals?direction=out&configID=' + config.id), token).then(response => {
AppDispatcher.dispatch({
type: 'signals/loaded',
data: response.signals
});
AppDispatcher.dispatch({
type: 'signals/start-load',
token: token,
param: '?direction=out&configID=' + config.id,
});
}
}
loadFiles(token, configs){

View file

@ -17,18 +17,19 @@
import React from 'react';
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
import _ from 'lodash';
import Dialog from '../common/dialogs/dialog';
class ImportConfigDialog extends React.Component {
imported = false;
valid = false;
constructor(props) {
super(props);
this.state = {
config: {}
config: {},
name: '',
};
}
@ -39,12 +40,13 @@ class ImportConfigDialog extends React.Component {
return;
}
this.props.onClose(this.state.config);
this.props.onClose(this.state);
}
resetState = () => {
this.setState({
config: {}
config: {},
name: ''
});
this.imported = false;
@ -58,46 +60,65 @@ class ImportConfigDialog extends React.Component {
}
// create file reader
const reader = new FileReader();
const self = this;
let reader = new FileReader();
let self = this;
reader.onload = event => {
const config = JSON.parse(event.target.result);
config.icID = this.props.ics.length > 0 ? this.props.ics[0]._id : null;
self.imported = true;
this.setState({ config: config });
self.valid = true;
this.setState({name: config.name, config: config });
};
reader.readAsText(file);
}
handleICChange = event => {
const config = this.state.config;
handleChange(e, index) {
this.setState({ [e.target.id]: e.target.value });
}
config.icID = event.target.value;
validateForm(target) {
// check all controls
let name = true;
this.setState({ config: config });
if (this.state.name === '') {
name = false;
}
this.valid = name;
// return state to control
if (target === 'name'){
return name;
}
}
render() {
return (
<Dialog show={this.props.show} title="Import Component Configuration" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={this.resetState} valid={this.imported}>
<Dialog
show={this.props.show}
title="Import Component Configuration"
buttonTitle="Import"
onClose={(c) => this.onClose(c)}
onReset={() => this.resetState()}
valid={this.valid} >
<form>
<FormGroup controlId='file'>
<FormLabel>Component Configuration File</FormLabel>
<FormControl type='file' onChange={this.loadFile} />
</FormGroup>
<FormGroup controlId='IC'>
<FormLabel>Infrastructure Component</FormLabel>
<FormControl disabled={this.imported === false} as='select' placeholder='Select infrastructure component' value={this.state.config.icID} onChange={this.handleICChange}>
{this.props.ics.map(ic => (
<option key={ic.id} value={ic.id}>{_.get(ic, 'properties.name') || _.get(ic, 'rawProperties.name')}</option>
))}
</FormControl>
<FormGroup controlId="name" >
<FormLabel>Name</FormLabel>
<FormControl
readOnly={!this.imported}
isValid={this.validateForm('name')}
type="text"
placeholder="Enter name"
value={this.state.name}
onChange={(e) => this.handleChange(e)}
/>
<FormControl.Feedback />
</FormGroup>
</form>
</Dialog>

View file

@ -18,4 +18,40 @@
import ArrayStore from '../common/array-store';
import DashboardsDataManager from './dashboards-data-manager';
export default new ArrayStore('dashboards', DashboardsDataManager);
class DashboardStore extends ArrayStore {
constructor() {
super('dashboards', DashboardsDataManager);
}
reduce(state, action) {
switch (action.type) {
case 'dashboards/start-add':
// Check if this is a recursive dashboard import or not
if (action.data.hasOwnProperty("widgets")) {
// import
let subObjects = []
let widgets = {}
widgets["widgets"] = action.data.widgets
subObjects.push(widgets)
delete action.data.widgets; // remove widgets from dashboard object
// action.data should now contain the dashboard and no sub-objects
// sub-objects are treated in add method of RestDataManager
this.dataManager.add(action.data, action.token,action.param, subObjects);
return state
} else {
// no import
return super.reduce(state, action);
}
default:
return super.reduce(state, action);
}
}
}
export default new DashboardStore();

View file

@ -16,5 +16,29 @@
******************************************************************************/
import RestDataManager from '../common/data-managers/rest-data-manager';
import AppDispatcher from "../common/app-dispatcher";
export default new RestDataManager('dashboard', '/dashboards');
class DashboardsDataManager extends RestDataManager{
constructor() {
super('dashboard', '/dashboards');
this.onLoad = this.onDashboardsLoad
}
onDashboardsLoad(data, token){
if (!Array.isArray(data)) {
data = [data];
}
for (let dashboard of data){
AppDispatcher.dispatch({
type: 'widgets/start-load',
token: token,
param: '?dashboardID=' + dashboard.id
});
}
}
}
export default new DashboardsDataManager();

View file

@ -67,27 +67,6 @@ class ImportDashboardDialog extends React.Component {
// read IC
const dashboard = JSON.parse(event.target.result);
/*let defaultIC = "";
if (self.props.configs != null) {
defaultIC = self.props.configs[0].icID;
}
dashboard.widgets.forEach(widget => {
switch (widget.type) {
case 'Value':
case 'Plot':
case 'Table':
case 'PlotTable':
case 'Gauge':
break;
default:
break;
}
});
*/
self.imported = true;
self.valid = true;
self.setState({ name: dashboard.name, widgets: dashboard.widgets, grid: dashboard.grid });
@ -107,21 +86,36 @@ class ImportDashboardDialog extends React.Component {
this.valid = name;
// return state to control
if (target === 'name') return name ? "success" : "error";
if (target === 'name'){
return name;
}
}
render() {
return (
<Dialog show={this.props.show} title="Import Dashboard" 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">
<FormLabel>Dashboard File</FormLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<FormGroup controlId="name" >
<FormLabel>Name</FormLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl
readOnly={!this.imported}
isValid={this.validateForm('name')}
type="text"
placeholder="Enter name"
value={this.state.name}
onChange={(e) => this.handleChange(e)}
/>
<FormControl.Feedback />
</FormGroup>
</form>

View file

@ -32,6 +32,7 @@ class ImportScenarioDialog extends React.Component {
name: '',
running: '',
configs: [],
dashboards: [],
startParameters: {}
};
}
@ -51,15 +52,6 @@ class ImportScenarioDialog extends React.Component {
}
handleChange(e, index) {
/*if (e.target.id === 'icID') {
const configs = this.state.configs;
configs[index].icID = JSON.parse(e.target.value);
this.setState({ configs: configs });
return;
}*/
this.setState({ [e.target.id]: e.target.value });
// check all controls
@ -94,7 +86,7 @@ class ImportScenarioDialog extends React.Component {
self.imported = true;
self.valid = true;
self.setState({ name: scenario.name, configs: scenario.configs, startParameters: scenario.startParameters, running: scenario.running });
self.setState({ name: scenario.name, configs: scenario.configs, dashboards: scenario.dashboards, startParameters: scenario.startParameters, running: scenario.running });
};
reader.readAsText(file);

View file

@ -19,4 +19,50 @@
import ScenariosDataManager from './scenarios-data-manager';
import ArrayStore from '../common/array-store';
export default new ArrayStore('scenarios', ScenariosDataManager);
class ScenarioStore extends ArrayStore{
constructor() {
super('scenarios', ScenariosDataManager);
}
reduce(state, action) {
switch (action.type) {
case 'scenarios/start-add':
// Check if this is a recursive scenario import or not
if (action.data.hasOwnProperty("configs") || action.data.hasOwnProperty("dashboards")) {
// import
let subObjects = []
let configs = {}
let dashboards = {}
if (action.data.hasOwnProperty("configs")){
configs["configs"] = action.data.configs
subObjects.push(configs)
delete action.data.configs; // remove configs from scenario object
}
if (action.data.hasOwnProperty("dashboards")){
dashboards["dashboards"] = action.data.dashboards
subObjects.push(dashboards)
delete action.data.dashboards; // remove dashboards from scenario object
}
// action.data should now contain the scenario and no sub-objects
// sub-objects are treated in add method of RestDataManager
this.dataManager.add(action.data, action.token,action.param, subObjects);
return state
} else {
// no import
return super.reduce(state, action);
}
default:
return super.reduce(state, action);
}
}
}
export default new ScenarioStore();

View file

@ -40,10 +40,12 @@ import DeleteDialog from '../common/dialogs/delete-dialog';
import EditConfigDialog from "../componentconfig/edit-config";
import EditSignalMapping from "../signal/edit-signal-mapping";
import FileStore from "../file/file-store"
import WidgetStore from "../widget/widget-store";
class Scenario extends React.Component {
static getStores() {
return [ ScenarioStore, ConfigStore, DashboardStore, ICStore, LoginStore, SignalStore, FileStore];
return [ ScenarioStore, ConfigStore, DashboardStore, ICStore, LoginStore, SignalStore, FileStore, WidgetStore];
}
static calculateState(prevState, props) {
@ -96,7 +98,6 @@ class Scenario extends React.Component {
}
componentDidMount() {
//load selected scenario
AppDispatcher.dispatch({
type: 'scenarios/start-load',
@ -104,29 +105,14 @@ class Scenario extends React.Component {
token: this.state.sessionToken
});
// load component configurations for selected scenario
AppDispatcher.dispatch({
type: 'configs/start-load',
token: this.state.sessionToken,
param: '?scenarioID='+this.state.scenario.id,
});
// load dashboards of selected scenario
AppDispatcher.dispatch({
type: 'dashboards/start-load',
token: this.state.sessionToken,
param: '?scenarioID='+this.state.scenario.id,
});
// load ICs to enable that component configs and dashboards work with them
AppDispatcher.dispatch({
type: 'ics/start-load',
token: this.state.sessionToken,
token: this.state.sessionToken
});
}
/* ##############################################
* Component Configuration modification methods
############################################## */
@ -180,33 +166,47 @@ class Scenario extends React.Component {
});
}
importConfig(config){
importConfig(data){
this.setState({ importConfigModal: false });
if (config == null) {
if (data == null) {
return;
}
config.scenario = this.state.scenario.id;
let newConfig = JSON.parse(JSON.stringify(data.config))
newConfig["scenarioID"] = this.state.scenario.id;
newConfig.name = data.name;
AppDispatcher.dispatch({
type: 'configs/start-add',
data: config,
data: newConfig,
token: this.state.sessionToken
});
this.setState({ scenario: {} }, () => {
AppDispatcher.dispatch({
type: 'scenarios/start-load',
data: this.props.match.params.scenario,
token: this.state.sessionToken
});
});
}
exportConfig(index) {
// filter properties
const config = Object.assign({}, this.state.configs[index]);
let config = JSON.parse(JSON.stringify(this.state.configs[index]));
let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10))));
signals.forEach((signal) => {
delete signal.configID;
delete signal.id;
})
// two separate lists for inputMapping and outputMapping
let inputSignals = signals.filter(s => s.direction === 'in');
let outputSignals = signals.filter(s => s.direction === 'out');
// add signal mappings to config
config["inputMapping"] = inputSignals;
config["outputMapping"] = outputSignals;
delete config.id;
delete config.scenarioID;
delete config.inputLength;
delete config.outputLength;
// show save dialog
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
@ -311,9 +311,12 @@ class Scenario extends React.Component {
this.setState({ importDashboardModal: false });
if (data) {
let newDashboard = JSON.parse(JSON.stringify(data));
newDashboard["scenarioID"] = this.state.scenario.id;
AppDispatcher.dispatch({
type: 'dashboards/start-add',
data,
data: newDashboard,
token: this.state.sessionToken,
});
}
@ -321,9 +324,16 @@ class Scenario extends React.Component {
exportDashboard(index) {
// filter properties
const dashboard = Object.assign({}, this.state.dashboards[index]);
let dashboard = JSON.parse(JSON.stringify(this.state.dashboards[index]));
// TODO get elements recursively
let widgets = JSON.parse(JSON.stringify(WidgetStore.getState().filter(w => w.dashboardID === parseInt(dashboard.id, 10))));
widgets.forEach((widget) => {
delete widget.dashboardID;
delete widget.id;
})
dashboard["widgets"] = widgets;
delete dashboard.scenarioID;
delete dashboard.id;
// show save dialog
const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' });

View file

@ -16,41 +16,36 @@
******************************************************************************/
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');
this.onLoad = this.onScenariosLoad
}
getComponentConfigs(token, id) {
RestAPI.get(this.makeURL('/scenarios/' + id + '/configs'), token).then(response => {
AppDispatcher.dispatch({
type: 'scenarios/configs',
configs: response.configs
});
}).catch(error => {
AppDispatcher.dispatch({
type: 'scenarios/configs-error',
error: error
});
});
}
onScenariosLoad(data, token){
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
});
});
}
if (!Array.isArray(data)) {
data = [data];
}
for (let scenario of data){
AppDispatcher.dispatch({
type: 'configs/start-load',
token: token,
param: '?scenarioID=' + scenario.id
});
AppDispatcher.dispatch({
type: 'dashboards/start-load',
token: token,
param: '?scenarioID=' + scenario.id
});
// TODO add dispatch for files
}
}
}
export default new ScenariosDataManager();

View file

@ -23,6 +23,10 @@ import FileSaver from 'file-saver';
import AppDispatcher from '../common/app-dispatcher';
import ScenarioStore from './scenario-store';
import LoginStore from '../user/login-store';
import DashboardStore from '../dashboard/dashboard-store';
import WidgetStore from "../widget/widget-store";
import ConfigStore from '../componentconfig/config-store';
import SignalStore from '../signal/signal-store'
import Icon from '../common/icon';
import Table from '../common/table';
@ -33,18 +37,20 @@ import ImportScenarioDialog from './import-scenario';
import DeleteDialog from '../common/dialogs/delete-dialog';
class Scenarios extends Component {
static getStores() {
return [ ScenarioStore, LoginStore ];
return [ScenarioStore, LoginStore, DashboardStore, WidgetStore, ConfigStore, SignalStore];
}
static calculateState() {
const scenarios = ScenarioStore.getState();
const sessionToken = LoginStore.getState().token;
return {
scenarios,
sessionToken,
scenarios: ScenarioStore.getState(),
dashboards: DashboardStore.getState(),
configs: ConfigStore.getState(),
sessionToken: LoginStore.getState().token,
newModal: false,
deleteModal: false,
@ -63,23 +69,20 @@ class Scenarios extends Component {
});
}
componentDidUpdate() {}
closeNewModal(data) {
this.setState({ newModal : false });
if (data) {
if(data) {
AppDispatcher.dispatch({
type: 'scenarios/start-add',
data,
token: this.state.sessionToken
data: data,
token: this.state.sessionToken,
});
}
this.setState({ newModal: false });
}
showDeleteModal(id) {
// get scenario by id
var deleteScenario;
let deleteScenario;
this.state.scenarios.forEach((scenario) => {
if (scenario.id === id) {
@ -97,6 +100,16 @@ class Scenarios extends Component {
return;
}
this.state.dashboards.forEach((dashboard) => {
if (dashboard.id === this.state.modalScenario.id) {
AppDispatcher.dispatch({
type: 'dashboards/start-remove',
data: dashboard,
token: this.state.sessionToken
})
}
});
AppDispatcher.dispatch({
type: 'scenarios/start-remove',
data: this.state.modalScenario,
@ -118,7 +131,7 @@ class Scenarios extends Component {
}
closeEditModal(data) {
this.setState({ editModal : false });
this.setState({ editModal: false });
if (data != null) {
AppDispatcher.dispatch({
@ -135,8 +148,8 @@ class Scenarios extends Component {
if (data) {
AppDispatcher.dispatch({
type: 'scenarios/start-add',
data,
token: this.state.sessionToken
data: data,
token: this.state.sessionToken,
});
}
}
@ -150,14 +163,54 @@ class Scenarios extends Component {
};
exportScenario(index) {
// filter properties
let scenario = Object.assign({}, this.state.scenarios[index]);
// copy by value by converting to JSON and back
// otherwise, IDs of state objects will be deleted
let scenario = JSON.parse(JSON.stringify(this.state.scenarios[index]));
let configs = JSON.parse(JSON.stringify(this.state.configs.filter(config => config.scenarioID === scenario.id)));
let dashboards = JSON.parse(JSON.stringify(this.state.dashboards.filter(dashb => dashb.scenarioID === scenario.id)));
// create JSON object and add component configs
delete scenario.id;
let jsonObj = scenario;
// TODO request missing scenario parameters (Dashboards and component configs) recursively for export
configs.forEach((config) => {
let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10))));
signals.forEach((signal) => {
delete signal.configID;
delete signal.id;
})
// show save dialog
const blob = new Blob([JSON.stringify(scenario, null, 2)], { type: 'application/json' });
// two separate lists for inputMapping and outputMapping
let inputSignals = signals.filter(s => s.direction === 'in');
let outputSignals = signals.filter(s => s.direction === 'out');
// add signal mappings to config
config["inputMapping"] = inputSignals;
config["outputMapping"] = outputSignals;
delete config.id;
delete config.scenarioID;
delete config.inputLength;
delete config.outputLength;
})
jsonObj["configs"] = configs;
// add Dashboards and Widgets to JSON object
dashboards.forEach((dboard) => {
let widgets = JSON.parse(JSON.stringify(WidgetStore.getState().filter(w => w.dashboardID === parseInt(dboard.id, 10))));
widgets.forEach((widget) => {
delete widget.dashboardID;
delete widget.id;
})
dboard["widgets"] = widgets;
delete dboard.scenarioID;
delete dboard.id;
});
jsonObj["dashboards"] = dashboards;
// create JSON string and show save dialog
const blob = new Blob([JSON.stringify(jsonObj, null, 2)], { type: 'application/json' });
FileSaver.saveAs(blob, 'scenario - ' + scenario.name + '.json');
}

View file

@ -27,7 +27,6 @@ class SignalsDataManager extends RestDataManager{
reloadConfig(token, data){
// request in signals
console.log("Reloading component config due to signal add/remove")
RestAPI.get(this.makeURL('/configs/' + data.configID), token).then(response => {
AppDispatcher.dispatch({
type: 'configs/edited',