From b8a45447109dfed0517c5c2976078af58befa433 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 24 Jun 2020 15:32:07 +0200 Subject: [PATCH 1/6] Querying VILLASnode API works, API endpoint hardcoded for now, processing of response missing #224 --- src/scenario/scenario.js | 36 ++++++++++++++++++++++++++++++ src/signal/signal-store.js | 27 ++++++++++++++++++++-- src/signal/signals-data-manager.js | 20 +++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 8d3495e..ee54a36 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -392,6 +392,37 @@ class Scenario extends React.Component { } } + signalsAutoConf(index){ + let componentConfig = this.state.configs[index]; + console.log("Signal AutoConf for CC: ", componentConfig); + + // determine host of infrastructure component + let ic = this.state.ics.filter(ic => ic.id === componentConfig.icID) + console.log("Signal AutoConf for IC: ", ic); + + let request = {}; + request["id"] = this.uuidv4(); + request["action"] = "config" + + // TODO add parameter for API host in data model of infrastructure component + let api = 'https://villas.k8s.eonerc.rwth-aachen.de/ws/api/v1' + + AppDispatcher.dispatch({ + type: 'signals/start-autoconfig', + data: request, + url: api + }); + + } + + 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 ############################################## */ @@ -441,6 +472,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 + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'signals/autoconfig-error', + error: error + }) + }) + } + } export default new SignalsDataManager() From c00c16ddb540ab5a3305cbe9c6f5a43ce2a6c88f Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 1 Jul 2020 13:10:46 +0200 Subject: [PATCH 2/6] receive config of VILLASnode based on apihost parameter of IC --- src/scenario/scenario.js | 11 ++--------- src/signal/signals-data-manager.js | 1 - 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index ee54a36..6f93ed7 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -394,23 +394,16 @@ class Scenario extends React.Component { signalsAutoConf(index){ let componentConfig = this.state.configs[index]; - console.log("Signal AutoConf for CC: ", componentConfig); - // determine host of infrastructure component - let ic = this.state.ics.filter(ic => ic.id === componentConfig.icID) - console.log("Signal AutoConf for IC: ", ic); - + let ic = this.state.ics.find(ic => ic.id === componentConfig.icID) let request = {}; request["id"] = this.uuidv4(); request["action"] = "config" - // TODO add parameter for API host in data model of infrastructure component - let api = 'https://villas.k8s.eonerc.rwth-aachen.de/ws/api/v1' - AppDispatcher.dispatch({ type: 'signals/start-autoconfig', data: request, - url: api + url: ic.apihost }); } diff --git a/src/signal/signals-data-manager.js b/src/signal/signals-data-manager.js index 653d051..94fa1b4 100644 --- a/src/signal/signals-data-manager.js +++ b/src/signal/signals-data-manager.js @@ -42,7 +42,6 @@ class SignalsDataManager extends RestDataManager{ // data contains the request data: { action, id, (request)} // See documentation of VILLASnode API: https://villas.fein-aachen.org/doc/node-dev-api-node.html - console.log("startAutoConfig: POST to ", url, ": ", data); RestAPI.post(url, data).then(response => { AppDispatcher.dispatch({ type: 'signals/autoconfig-loaded', From f038cfe0c87f3736a4f55bf285af34d7c16dac77 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 1 Jul 2020 13:56:06 +0200 Subject: [PATCH 3/6] Add notifications, auto conf only for VILLASnode ICs #224 --- src/scenario/scenario.js | 14 ++++++++++++++ src/signal/signal-store.js | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 6f93ed7..8c91c95 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -42,6 +42,7 @@ 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"; +import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; class Scenario extends React.Component { @@ -396,6 +397,19 @@ class Scenario extends React.Component { 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 request = {}; request["id"] = this.uuidv4(); request["action"] = "config" diff --git a/src/signal/signal-store.js b/src/signal/signal-store.js index c947e56..e41b10c 100644 --- a/src/signal/signal-store.js +++ b/src/signal/signal-store.js @@ -40,6 +40,14 @@ class SignalStore extends ArrayStore{ case 'signals/autoconfig-loaded': console.log("AutoConfig Loaded: ", action.data) // TODO save signal config contained in action.data + + const SIGNAL_AUTOCONF_INFO_NOTIFICATION = { + title: 'Signal configuration loaded successfully.', + message: '', + level: 'info' + }; + NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_INFO_NOTIFICATION); + return super.reduce(state, action); case 'signals/autoconfig-error': From cc156d751bcb6e880ed35a272eb31778021c2f05 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 6 Jul 2020 11:33:11 +0200 Subject: [PATCH 4/6] implement signal auto config functionality based on response from VILLASnode API (to be tested) #224 --- src/scenario/scenario.js | 8 +- src/signal/signal-store.js | 10 +-- src/signal/signals-data-manager.js | 129 ++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 12 deletions(-) diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 8c91c95..d509e52 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -410,14 +410,18 @@ class Scenario extends React.Component { return; } + let splitHost = ic.host.split("/") let request = {}; request["id"] = this.uuidv4(); - request["action"] = "config" + request["action"] = "nodes" AppDispatcher.dispatch({ type: 'signals/start-autoconfig', data: request, - url: ic.apihost + url: ic.apihost, + socketname: splitHost[splitHost.length -1], + token: this.state.sessionToken, + configID: componentConfig.id }); } diff --git a/src/signal/signal-store.js b/src/signal/signal-store.js index e41b10c..ff4d228 100644 --- a/src/signal/signal-store.js +++ b/src/signal/signal-store.js @@ -34,19 +34,13 @@ class SignalStore extends ArrayStore{ return super.reduce(state, action); case 'signals/start-autoconfig': - this.dataManager.startAutoConfig(action.data, action.url) + this.dataManager.startAutoConfig(action.data, action.url, action.socketname, action.token, action.configID) return super.reduce(state, action); case 'signals/autoconfig-loaded': console.log("AutoConfig Loaded: ", action.data) // TODO save signal config contained in action.data - - const SIGNAL_AUTOCONF_INFO_NOTIFICATION = { - title: 'Signal configuration loaded successfully.', - message: '', - level: 'info' - }; - NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_INFO_NOTIFICATION); + this.dataManager.saveSignals(action.data, action.token, action.configID, action.socketname); return super.reduce(state, action); diff --git a/src/signal/signals-data-manager.js b/src/signal/signals-data-manager.js index 94fa1b4..c8f7127 100644 --- a/src/signal/signals-data-manager.js +++ b/src/signal/signals-data-manager.js @@ -18,6 +18,7 @@ import RestDataManager from '../common/data-managers/rest-data-manager'; import RestAPI from "../common/api/rest-api"; import AppDispatcher from "../common/app-dispatcher"; +import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; class SignalsDataManager extends RestDataManager{ @@ -36,7 +37,7 @@ class SignalsDataManager extends RestDataManager{ } - startAutoConfig(data, url){ + startAutoConfig(data, url, socketname, token, configID){ // This function queries the VILLASnode API to obtain the configuration of the VILLASnode located at url // Endpoint: http[s]://server:port/api/v1 (to be generated based on IC host, port 4000) // data contains the request data: { action, id, (request)} @@ -45,7 +46,10 @@ class SignalsDataManager extends RestDataManager{ RestAPI.post(url, data).then(response => { AppDispatcher.dispatch({ type: 'signals/autoconfig-loaded', - data: response + data: response, + token: token, + socketname: socketname, + configID: configID }); }).catch(error => { AppDispatcher.dispatch({ @@ -55,6 +59,127 @@ class SignalsDataManager extends RestDataManager{ }) } + 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() From 1f6a4a07ba47cb0ba5a81e9a37dd6c007dbd8f00 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 6 Jul 2020 12:50:40 +0200 Subject: [PATCH 5/6] Add API host parameter to IC table, make the parameter editable --- src/ic/edit-ic.js | 11 +++++++++++ src/ic/ics.js | 1 + 2 files changed, 12 insertions(+) diff --git a/src/ic/edit-ic.js b/src/ic/edit-ic.js index 404642c..e91c8ef 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: '', properties: {}, @@ -50,6 +51,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; } @@ -77,6 +82,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, properties: _.merge({}, _.get(this.props.ic, 'rawProperties'), _.get(this.props.ic, 'properties')) @@ -97,6 +103,11 @@ class EditICDialog extends React.Component { this.handleChange(e)} /> + + API Host + this.handleChange(e)} /> + + Category (e.g. Simulator, Gateway, ...) this.handleChange(e)} /> diff --git a/src/ic/ics.js b/src/ic/ics.js index e24cd93..5cd7719 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -280,6 +280,7 @@ class InfrastructureComponents extends Component { {/* */} + Date: Sun, 1 Nov 2020 15:27:05 +0100 Subject: [PATCH 6/6] Add column for IC uptime, WIP: add IC infos and controls dialog #266 --- src/ic/ic-dialog.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/ic/ics.js | 29 ++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/ic/ic-dialog.js 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..e873f2b 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,11 +328,12 @@ 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)}/> {/* */} @@ -343,7 +360,7 @@ class InfrastructureComponents extends Component {
this.runAction(action)} actions={[ { id: '-1', title: 'Select command', data: { action: 'none' } }, { id: '0', title: 'Reset', data: { action: 'reset' } }, @@ -369,6 +386,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)} />