mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'new-ic-data-model' of git.rwth-aachen.de:acs/public/villas/web into new-ic-data-model
This commit is contained in:
commit
a6cbf9ded2
6 changed files with 299 additions and 7 deletions
|
@ -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 {
|
|||
<FormControl type="text" placeholder={this.props.ic.host} value={this.state.host || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="apihost">
|
||||
<FormLabel column={false}>API Host</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.apihost} value={this.state.apihost || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="category">
|
||||
<FormLabel column={false}>Category</FormLabel>
|
||||
<FormControl as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
|
||||
|
|
45
src/ic/ic-dialog.js
Normal file
45
src/ic/ic-dialog.js
Normal file
|
@ -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 (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Infos and Controls"
|
||||
buttonTitle="Save"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
valid={true}
|
||||
size='lg'
|
||||
>
|
||||
<form>
|
||||
<FormLabel>Infos and Controls</FormLabel>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ICDialog;
|
|
@ -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 <span>{uptime + "s"}</span>
|
||||
}
|
||||
else{
|
||||
return <Badge variant="secondary">Unknown</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
|
@ -312,14 +328,16 @@ class InfrastructureComponents extends Component {
|
|||
|
||||
<Table data={this.state.ics}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onICChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKeys={['name', 'rawProperties.name']} />
|
||||
<TableColumn title='Name' dataKeys={['name', 'rawProperties.name']} clickable={true} onClick={index => this.setState({ icModal: true, modalIC: this.state.ics[index], modalIndex: index })}/>
|
||||
<TableColumn title='State' labelKey='state' tooltipKey='error' labelStyle={(state, component) => this.stateLabelStyle(state, component)} />
|
||||
<TableColumn title='Category' dataKeys={['category', 'rawProperties.category']} />
|
||||
<TableColumn title='Type' dataKeys={['type', 'rawProperties.type']} />
|
||||
<TableColumn title='Managed externally' dataKey='managedexternally' modifier={(managedexternally) => this.modifyManagedExternallyColumn(managedexternally)} width='105' />
|
||||
<TableColumn title='Uptime' dataKey='uptime' modifier={(uptime) => this.modifyUptimeColumn(uptime)}/>
|
||||
<TableColumn title='Location' dataKeys={['properties.location', 'rawProperties.location']} />
|
||||
{/* <TableColumn title='Realm' dataKeys={['properties.realm', 'rawProperties.realm']} /> */}
|
||||
<TableColumn title='WebSocket Endpoint' dataKey='host' />
|
||||
<TableColumn title='API Host' dataKey='apihost' />
|
||||
<TableColumn title='Last Update' dataKey='stateUpdateAt' modifier={(stateUpdateAt) => this.stateUpdateModifier(stateUpdateAt)} />
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
|
@ -343,7 +361,7 @@ class InfrastructureComponents extends Component {
|
|||
<div style={{ float: 'left' }}>
|
||||
<ICAction
|
||||
runDisabled={this.state.selectedICs.length === 0}
|
||||
runAction={this.runAction}
|
||||
runAction={action => 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 {
|
|||
<NewICDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} />
|
||||
<EditICDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} ic={this.state.modalIC} />
|
||||
<ImportICDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
|
||||
<ICDialog show={this.state.icModal} onClose={data => this.closeICModal(data)} ic={this.state.modalIC} token={this.state.sessionToken} />
|
||||
|
||||
|
||||
<DeleteDialog title="infrastructure-component" name={_.get(this.state.modalIC, 'properties.name') || _.get(this.state.modalIC, 'rawProperties.name') || 'Unknown'} managedexternally={this.state.modalIC.managedexternally} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
</div>
|
||||
|
|
|
@ -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 })}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Signal AutoConf'
|
||||
exportButton
|
||||
onExport={(index) => this.signalsAutoConf(index)}
|
||||
/>
|
||||
<TableColumn title='Infrastructure Component' dataKey='icID' modifier={(icID) => this.getICName(icID)} />
|
||||
<TableColumn
|
||||
title=''
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import ArrayStore from '../common/array-store';
|
||||
import SignalsDataManager from './signals-data-manager'
|
||||
import NotificationsDataManager from "../common/data-managers/notifications-data-manager";
|
||||
|
||||
class SignalStore extends ArrayStore{
|
||||
constructor() {
|
||||
|
@ -26,10 +27,34 @@ class SignalStore extends ArrayStore{
|
|||
reduce(state, action) {
|
||||
switch (action.type) {
|
||||
case 'signals/added':
|
||||
SignalsDataManager.reloadConfig(action.token, action.data);
|
||||
this.dataManager.reloadConfig(action.token, action.data);
|
||||
return super.reduce(state, action);
|
||||
case 'signals/removed':
|
||||
SignalsDataManager.reloadConfig(action.token, action.data);
|
||||
this.dataManager.reloadConfig(action.token, action.data);
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'signals/start-autoconfig':
|
||||
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
|
||||
this.dataManager.saveSignals(action.data, action.token, action.configID, action.socketname);
|
||||
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'signals/autoconfig-error':
|
||||
if (action.error && !action.error.handled && action.error.response) {
|
||||
|
||||
const SIGNAL_AUTOCONF_ERROR_NOTIFICATION = {
|
||||
title: 'Failed to load signal config ',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
};
|
||||
NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
default:
|
||||
|
|
|
@ -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,6 +37,149 @@ class SignalsDataManager extends RestDataManager{
|
|||
|
||||
}
|
||||
|
||||
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)}
|
||||
// See documentation of VILLASnode API: https://villas.fein-aachen.org/doc/node-dev-api-node.html
|
||||
|
||||
RestAPI.post(url, data).then(response => {
|
||||
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()
|
||||
|
|
Loading…
Add table
Reference in a new issue