diff --git a/src/ic/edit-ic.js b/src/ic/edit-ic.js index 7386131..1bc6f0d 100644 --- a/src/ic/edit-ic.js +++ b/src/ic/edit-ic.js @@ -31,6 +31,7 @@ class EditICDialog extends React.Component { this.state = { name: '', host: '', + apihost: '', type: '', category: '', managedexternally: false, @@ -51,6 +52,10 @@ class EditICDialog extends React.Component { data.host = this.state.host; } + if (this.state.apihost != null && this.state.apihost !== "" && this.state.apihost !== "http://" && this.state.apihost !== this.props.ic.apihost) { + data.apihost = this.state.apihost; + } + if (this.state.type != null && this.state.type !== "" && this.state.type !== this.props.ic.type) { data.type = this.state.type; } @@ -91,6 +96,7 @@ class EditICDialog extends React.Component { this.setState({ name: this.props.ic.name, host: this.props.ic.host, + apihost: this.props.ic.apihost, type: this.props.ic.type, category: this.props.ic.category, managedexternally: false, @@ -148,6 +154,11 @@ class EditICDialog extends React.Component { this.handleChange(e)} /> + + API Host + this.handleChange(e)} /> + + Category this.handleChange(e)}> diff --git a/src/ic/ic-dialog.js b/src/ic/ic-dialog.js new file mode 100644 index 0000000..b134f95 --- /dev/null +++ b/src/ic/ic-dialog.js @@ -0,0 +1,45 @@ +import React from 'react'; +import {FormLabel} from 'react-bootstrap'; +import Dialog from '../common/dialogs/dialog'; + + +class ICDialog extends React.Component { + valid = true; + + constructor(props) { + super(props); + + this.state = { + ic: props.ic + }; + } + + onClose(canceled) { + this.props.onClose(); + } + + handleChange(e) { + + } + + + render() { + + return ( + this.onClose(c)} + valid={true} + size='lg' + > +
+ Infos and Controls +
+
+ ); + } +} + +export default ICDialog; diff --git a/src/ic/ics.js b/src/ic/ics.js index 44173e7..4696ef6 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -17,7 +17,7 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; -import { Button } from 'react-bootstrap'; +import { Button, Badge } from 'react-bootstrap'; import FileSaver from 'file-saver'; import _ from 'lodash'; import moment from 'moment' @@ -31,6 +31,7 @@ import TableColumn from '../common/table-column'; import NewICDialog from './new-ic'; import EditICDialog from './edit-ic'; import ImportICDialog from './import-ic'; +import ICDialog from './ic-dialog'; import ICAction from './ic-action'; import DeleteDialog from '../common/dialogs/delete-dialog'; @@ -78,6 +79,7 @@ class InfrastructureComponents extends Component { ics: ics, modalIC: {}, deleteModal: false, + icModal: false, selectedICs: [], currentUser: JSON.parse(localStorage.getItem("currentUser")) }; @@ -99,7 +101,7 @@ class InfrastructureComponents extends Component { refresh() { - if (this.state.editModal || this.state.deleteModal){ + if (this.state.editModal || this.state.deleteModal || this.state.icModal){ // do nothing since a dialog is open at the moment } else { @@ -139,6 +141,10 @@ class InfrastructureComponents extends Component { } } + closeICModal(data){ + this.setState({ icModal : false }); + } + closeDeleteModal(confirmDelete){ this.setState({ deleteModal: false }); @@ -200,7 +206,7 @@ class InfrastructureComponents extends Component { this.setState({ selectedICs: selectedICs }); } - runAction = action => { + runAction(action) { for (let index of this.state.selectedICs) { AppDispatcher.dispatch({ type: 'ics/start-action', @@ -301,6 +307,16 @@ class InfrastructureComponents extends Component { } + modifyUptimeColumn(uptime){ + if(uptime >= 0){ + return {uptime + "s"} + } + else{ + return Unknown + } + } + + render() { const buttonStyle = { marginLeft: '10px' @@ -312,14 +328,16 @@ class InfrastructureComponents extends Component { this.onICChecked(index, event)} width='30' /> - + this.setState({ icModal: true, modalIC: this.state.ics[index], modalIndex: index })}/> this.stateLabelStyle(state, component)} /> this.modifyManagedExternallyColumn(managedexternally)} width='105' /> + this.modifyUptimeColumn(uptime)}/> {/* */} + this.stateUpdateModifier(stateUpdateAt)} /> {this.state.currentUser.role === "Admin" ? this.runAction(action)} actions={[ { id: '-1', title: 'Select command', data: { action: 'none' } }, { id: '0', title: 'Reset', data: { action: 'reset' } }, @@ -369,6 +387,8 @@ class InfrastructureComponents extends Component { this.closeNewModal(data)} /> this.closeEditModal(data)} ic={this.state.modalIC} /> this.closeImportModal(data)} /> + this.closeICModal(data)} ic={this.state.modalIC} token={this.state.sessionToken} /> + this.closeDeleteModal(e)} /> diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index cd8fe80..858a7a6 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -44,6 +44,7 @@ import EditSignalMapping from "../signal/edit-signal-mapping"; import FileStore from "../file/file-store" import WidgetStore from "../widget/widget-store"; import { Redirect } from 'react-router-dom'; +import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; class Scenario extends React.Component { @@ -460,6 +461,47 @@ class Scenario extends React.Component { // TODO do we need this if the dispatches happen in the dialog? } + signalsAutoConf(index){ + let componentConfig = this.state.configs[index]; + // determine host of infrastructure component + let ic = this.state.ics.find(ic => ic.id === componentConfig.icID) + if(!ic.type.includes("VILLASnode") && !ic.type.includes("villasnode") && !ic.type.includes("VILLASNODE")){ + let message = "Cannot autoconfigure signals for IC type " + ic.type + " of category " + ic.category + ". This is only possible for gateway ICs of type 'VILLASnode'." + console.warn(message); + + const SIGNAL_AUTOCONF_WARN_NOTIFICATION = { + title: 'Failed to load signal config for IC ' + ic.name, + message: message, + level: 'warning' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_WARN_NOTIFICATION); + return; + } + + let splitHost = ic.host.split("/") + let request = {}; + request["id"] = this.uuidv4(); + request["action"] = "nodes" + + AppDispatcher.dispatch({ + type: 'signals/start-autoconfig', + data: request, + url: ic.apihost, + socketname: splitHost[splitHost.length -1], + token: this.state.sessionToken, + configID: componentConfig.id + }); + + } + + uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + // eslint-disable-next-line + var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + /* ############################################## * File modification methods ############################################## */ @@ -587,6 +629,11 @@ class Scenario extends React.Component { editButton onEdit={index => this.setState({ editInputSignalsModal: true, modalConfigData: this.state.configs[index], modalConfigIndex: index })} /> + this.signalsAutoConf(index)} + /> this.getICName(icID)} /> { + AppDispatcher.dispatch({ + type: 'signals/autoconfig-loaded', + data: response, + token: token, + socketname: socketname, + configID: configID + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'signals/autoconfig-error', + error: error + }) + }) + } + + saveSignals(data, token, configID, socketname){ + // data.response contains the response from the VILLASnode API, an array of node configurations + + if(!data.hasOwnProperty("response")){ + const SIGNAL_AUTOCONF_ERROR_NOTIFICATION = { + title: 'Failed to load signal config ', + message: 'VILLASnode returned no response field.', + level: 'error' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_ERROR_NOTIFICATION); + return; + } + + let configured = false; + let error = false; + for(let nodeConfig of data.response){ + if(!nodeConfig.hasOwnProperty("name")){ + console.warn("Could not parse the following node config because it lacks a name parameter:", nodeConfig); + } else if(nodeConfig.name === socketname){ + if(configured){ + const SIGNAL_AUTOCONF_WARNING_NOTIFICATION = { + title: 'There might be a problem with the signal auto-config', + message: 'VILLASnode returned multiple node configurations for the websocket ' + socketname + '. This is a problem of the VILLASnode.', + level: 'warning' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_WARNING_NOTIFICATION); + continue; + } + // signals are not yet configured: + console.log("Adding signals of websocket: ", nodeConfig); + let index_in = 1 + let index_out = 1 + + if(!nodeConfig.in.hasOwnProperty("signals")){ + const SIGNAL_AUTOCONF_ERROR_NOTIFICATION = { + title: 'Failed to load in signal config ', + message: 'No field for in signals contained in response.', + level: 'error' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_ERROR_NOTIFICATION); + error = true; + } else{ + + // add all in signals + for(let inSig of nodeConfig.in.signals) { + console.log("adding input signal:", inSig); + + if (inSig.enabled) { + let newSignal = { + configID: configID, + direction: 'in', + name: inSig.hasOwnProperty("name") ? inSig.name : "in_" + String(index_in), + unit: inSig.hasOwnProperty("unit") ? inSig.unit : '-', + index: index_in, + scalingFactor: 1.0 + }; + + AppDispatcher.dispatch({ + type: 'signals/start-add', + data: newSignal, + token: token + }); + + index_in++; + } + } + } + + if(!nodeConfig.out.hasOwnProperty("signals")){ + const SIGNAL_AUTOCONF_ERROR_NOTIFICATION = { + title: 'Failed to load out signal config ', + message: 'No field for out signals contained in response.', + level: 'error' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_ERROR_NOTIFICATION); + error=true; + }else { + + // add all out signals + + for (let outSig of nodeConfig.out.signals) { + console.log("adding output signal:", outSig); + + if (outSig.enabled) { + let newSignal = { + configID: configID, + direction: 'out', + name: outSig.hasOwnProperty("name") ? outSig.name : "out_" + String(index_out), + unit: outSig.hasOwnProperty("unit") ? outSig.unit : '-', + index: index_out, + scalingFactor: 1.0 + }; + + AppDispatcher.dispatch({ + type: 'signals/start-add', + data: newSignal, + token: token + }); + + index_out++; + } + } + } + + console.log("Configured", index_in-1, "input signals and", index_out-1, "output signals"); + configured=true; + } + + } + + if(!error) { + const SIGNAL_AUTOCONF_INFO_NOTIFICATION = { + title: 'Signal configuration loaded successfully.', + message: '', + level: 'info' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_INFO_NOTIFICATION); + } + + } + } export default new SignalsDataManager()