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 'master' of git.rwth-aachen.de:acs/public/villas/web

This commit is contained in:
irismarie 2021-02-12 17:39:17 +01:00
commit ab0ed00231
9 changed files with 261 additions and 23518 deletions

23362
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,7 @@ class DeleteDialog extends React.Component {
<Modal.Body>
Are you sure you want to delete the {this.props.title} <strong>'{this.props.name}'</strong>?
<Collapse isOpened={this.props.managedexternally} >
<FormLabel size="sm">The IC will be deleted if the respective VILLAScontroller sends "gone" state and no component config is using the IC anymore</FormLabel>
<FormLabel size="sm">The IC will be deleted if the respective manager sends "gone" state and no component config is using the IC anymore</FormLabel>
</Collapse>
</Modal.Body>

View file

@ -126,7 +126,7 @@ class CustomTable extends Component {
inline
disabled = {isDisabled}
checked={checkboxKey ? data[checkboxKey] : null}
onChange={e => child.props.onChecked(index, e)}
onChange={e => child.props.onChecked(data, e)}
/>);
}

View file

@ -122,8 +122,8 @@ class EditICDialog extends React.Component {
case "simulator":
typeOptions = ["dummy","generic","dpsim","rtlab","rscad", "opalrt"];
break;
case "controller":
typeOptions = ["kubernetes","villas-controller"];
case "manager":
typeOptions = ["villas-node","villas-relay","generic"];
break;
case "gateway":
typeOptions = ["villas-node","villas-relay"];
@ -158,10 +158,10 @@ class EditICDialog extends React.Component {
<FormLabel column={false}>Category</FormLabel>
<FormControl as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
<option>simulator</option>
<option>controller</option>
<option>service</option>
<option>gateway</option>
<option>equipment</option>
<option>manager</option>
</FormControl>
</FormGroup>
<FormGroup controlId="type">

View file

@ -16,75 +16,93 @@
******************************************************************************/
import React from 'react';
import { Button, ButtonToolbar, DropdownButton, Dropdown } from 'react-bootstrap';
import TimePicker from 'react-bootstrap-time-picker'
import { Button, ButtonToolbar, DropdownButton, Dropdown, InputGroup, FormControl } from 'react-bootstrap';
class ICAction extends React.Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {
selectedAction: null,
selectedDelay: 0
};
let t = new Date()
Number.prototype.pad = function(size) {
var s = String(this);
while (s.length < (size || 2)) {s = "0" + s;}
return s;
}
static getDerivedStateFromProps(props, state){
if (state.selectedAction == null) {
if (props.actions != null && props.actions.length > 0) {
return{
selectedAction: props.actions[0]
};
}
}
return null
}
let time = new Date();
time.setMinutes(5 * Math.round(time.getMinutes() / 5 + 1))
setAction = id => {
// search action
for (let action of this.props.actions) {
if (action.id === id) {
this.setState({ selectedAction: action });
}
}
this.state = {
selectedAction: null,
time: time
};
}
setDelayForAction = time => {
// time in int format: (hours * 3600 + minutes * 60 + seconds)
this.setState({selectedDelay: time})
static getDerivedStateFromProps(props, state) {
if (state.selectedAction == null) {
if (props.actions != null && props.actions.length > 0) {
return {
selectedAction: props.actions[0]
};
}
}
return null
}
render() {
let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1"
const actionList = this.props.actions.map(action => (
<Dropdown.Item key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
{action.title}
</Dropdown.Item>
));
return <div>
{"Select delay for command execution (Format hh:mm, max 1h):"}
<TimePicker
format={24}
initialValue={this.state.selectedDelay}
value={this.state.selectedDelay}
start={"00:00"}
end={"01:00"}
step={1}
onChange={this.setDelayForAction}
/>
<ButtonToolbar>
<DropdownButton title={this.state.selectedAction != null ? this.state.selectedAction.title : ''} id="action-dropdown" onSelect={this.setAction}>
{actionList}
</DropdownButton>
<Button style={{ marginLeft: '5px' }} disabled={sendCommandDisabled} onClick={() => this.props.runAction(this.state.selectedAction, this.state.selectedDelay)}>Send command</Button>
</ButtonToolbar>
</div>;
setAction = id => {
// search action
for (let action of this.props.actions) {
if (action.id === id) {
this.setState({ selectedAction: action });
}
}
};
setTimeForAction = (time) => {
this.setState({ time: new Date(time) })
}
render() {
let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1"
let time = this.state.time.getFullYear().pad(4) + '-' +
this.state.time.getMonth().pad(2) + '-' +
this.state.time.getDay().pad(2) + 'T' +
this.state.time.getHours().pad(2) + ':' +
this.state.time.getMinutes().pad(2);
const actionList = this.props.actions.map(action => (
<Dropdown.Item key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
{action.title}
</Dropdown.Item>
));
return <div>
<InputGroup>
<InputGroup.Prepend>
<DropdownButton
variant="outline-secondary"
title={this.state.selectedAction != null ? this.state.selectedAction.title : ''}
id="action-dropdown"
onSelect={this.setAction}>
{actionList}
</DropdownButton>
<FormControl
type="datetime-local"
variant="outline-secondary"
value={time}
onChange={this.setTimeForAction} />
</InputGroup.Prepend>
<Button
variant="outline-secondary"
disabled={sendCommandDisabled}
onClick={() => this.props.runAction(this.state.selectedAction, this.state.time)}>Run</Button>
</InputGroup>
<small className="text-muted">Select time for synced command execution</small>
</div>;
}
}
export default ICAction;

View file

@ -24,8 +24,14 @@ class IcsDataManager extends RestDataManager {
super('ic', '/ic');
}
doActions(ic, action, token = null) {
RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), action, token).then(response => {
doActions(ic, actions, token = null) {
for (let action of actions) {
if (action.when)
// Send timestamp as Unix Timestamp
action.when = Math.round(action.when.getTime() / 1000);
}
RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), actions, token).then(response => {
AppDispatcher.dispatch({
type: 'ics/action-started',
data: response

View file

@ -75,17 +75,26 @@ class InfrastructureComponents extends Component {
}
});
let externalICs = ics.find(ic => ic.managedexternally === true)
// collect number of external ICs
let externalICs = ics.filter(ic => ic.managedexternally === true)
let numberOfExternalICs = externalICs.length;
// collect all IC categories
let managers = ics.filter(ic => ic.category === "manager")
let gateways = ics.filter(ic => ic.category === "gateway")
let simulators = ics.filter(ic => ic.category === "simulator")
let services = ics.filter(ic => ic.category === "service")
let equipment = ics.filter(ic => ic.category === "equipment")
let numberOfExternalICs = 0
if (externalICs !== undefined && !Array.isArray(externalICs)) {
externalICs = [externalICs];
numberOfExternalICs = externalICs.length;
}
return {
sessionToken: localStorage.getItem("token"),
ics: ics,
managers: managers,
gateways: gateways,
simulators: simulators,
services: services,
equipment: equipment,
numberOfExternalICs,
modalIC: {},
deleteModal: false,
@ -136,7 +145,6 @@ class InfrastructureComponents extends Component {
}
}
closeNewModal(data) {
this.setState({ newModal : false });
@ -205,7 +213,9 @@ class InfrastructureComponents extends Component {
}
}
onICChecked(index, event) {
onICChecked(ic, event) {
let index = this.state.ics.indexOf(ic);
const selectedICs = Object.assign([], this.state.selectedICs);
for (let key in selectedICs) {
if (selectedICs[key] === index) {
@ -230,8 +240,10 @@ class InfrastructureComponents extends Component {
this.setState({ selectedICs: selectedICs });
}
runAction(action) {
runAction(action, when) {
for (let index of this.state.selectedICs) {
action.when = when;
AppDispatcher.dispatch({
type: 'ics/start-action',
ic: this.state.ics[index],
@ -251,7 +263,6 @@ class InfrastructureComponents extends Component {
}
stateLabelStyle(state, component){
var style = [ 'badge' ];
if (InfrastructureComponents.isICOutdated(component) && state !== 'shutdown') {
@ -296,7 +307,6 @@ class InfrastructureComponents extends Component {
default:
style.push('badge-default');
/* Possible states of ICs
* 'error': ['resetting', 'error'],
'idle': ['resetting', 'error', 'idle', 'starting', 'shuttingdown'],
@ -322,13 +332,11 @@ class InfrastructureComponents extends Component {
}
modifyManagedExternallyColumn(managedExternally, component){
if(managedExternally){
return <Icon icon='check' />
} else {
return ""
}
}
modifyUptimeColumn(uptime, component){
@ -336,7 +344,7 @@ class InfrastructureComponents extends Component {
let momentDurationFormatSetup = require("moment-duration-format");
momentDurationFormatSetup(moment)
let timeString = moment.duration(uptime, "seconds").format();
let timeString = moment.duration(uptime, "seconds").humanize();
return <span>{timeString}</span>
}
else{
@ -355,7 +363,6 @@ class InfrastructureComponents extends Component {
}
sendControlCommand(command,ic){
if(command === "restart"){
AppDispatcher.dispatch({
type: 'ics/restart',
@ -369,7 +376,6 @@ class InfrastructureComponents extends Component {
token: this.state.sessionToken,
});
}
}
isExternalIC(index){
@ -377,12 +383,80 @@ class InfrastructureComponents extends Component {
return ic.managedexternally
}
getICCategoryTable(ics, editable, title){
if (ics && ics.length > 0) {
return (<div>
<h2>{title}</h2>
<Table data={ics}>
<TableColumn
checkbox
checkboxDisabled={(index) => this.isExternalIC(index)}
onChecked={(ic, event) => this.onICChecked(ic, event)}
width='30'
/>
<TableColumn
title='Name'
dataKeys={['name']}
modifier={(name, component) => this.modifyNameColumn(name, component)}
/>
<TableColumn
title='State'
labelKey='state'
tooltipKey='error'
labelStyle={(state, component) => this.stateLabelStyle(state, component)}
/>
<TableColumn
title='Type'
dataKeys={['type']}
/>
<TableColumn
title='Uptime'
dataKey='uptime'
modifier={(uptime, component) => this.modifyUptimeColumn(uptime, component)}
/>
<TableColumn
title='Last Update'
dataKey='stateUpdateAt'
modifier={(stateUpdateAt, component) => this.stateUpdateModifier(stateUpdateAt, component)}
/>
{this.state.currentUser.role === "Admin" && editable ?
<TableColumn
width='200'
editButton
exportButton
deleteButton
onEdit={index => this.setState({editModal: true, modalIC: ics[index], modalIndex: index})}
onExport={index => this.exportIC(index)}
onDelete={index => this.setState({deleteModal: true, modalIC: ics[index], modalIndex: index})}
/>
:
<TableColumn
width='100'
exportButton
onExport={index => this.exportIC(index)}
/>
}
</Table>
</div>);
} else {
return <div/>
}
}
render() {
const buttonStyle = {
marginLeft: '10px'
};
let managerTable = this.getICCategoryTable(this.state.managers, false, "IC Managers")
let simulatorTable = this.getICCategoryTable(this.state.simulators, true, "Simulators")
let gatewayTable = this.getICCategoryTable(this.state.gateways, true, "Gateways")
let serviceTable = this.getICCategoryTable(this.state.services, true, "Services")
let equipmentTable = this.getICCategoryTable(this.state.equipment, true, "Equipment")
return (
<div className='section'>
<h1>Infrastructure Components
@ -407,69 +481,20 @@ class InfrastructureComponents extends Component {
(<span> </span>)
}
</h1>
<Table data={this.state.ics}>
<TableColumn
checkbox
checkboxDisabled={(index) => this.isExternalIC(index)}
onChecked={(index, event) => this.onICChecked(index, event)}
width='30'
/>
<TableColumn
title='Name'
dataKeys={['name']}
modifier={(name, component) => this.modifyNameColumn(name, component)}
/>
<TableColumn
title='State'
labelKey='state'
tooltipKey='error'
labelStyle={(state, component) => this.stateLabelStyle(state, component)}
/>
<TableColumn
title='Category'
dataKeys={['category']}
/>
<TableColumn
title='Type'
dataKeys={['type']}
/>
<TableColumn
title='Uptime'
dataKey='uptime'
modifier={(uptime, component) => this.modifyUptimeColumn(uptime, component)}
/>
<TableColumn
title='Last Update'
dataKey='stateUpdateAt'
modifier={(stateUpdateAt, component) => this.stateUpdateModifier(stateUpdateAt, component)}
/>
{this.state.currentUser.role === "Admin" ?
<TableColumn
width='200'
editButton
exportButton
deleteButton
onEdit={index => this.setState({editModal: true, modalIC: this.state.ics[index], modalIndex: index})}
onExport={index => this.exportIC(index)}
onDelete={index => this.setState({deleteModal: true, modalIC: this.state.ics[index], modalIndex: index})}
/>
:
<TableColumn
width='100'
exportButton
onExport={index => this.exportIC(index)}
/>
}
</Table>
{managerTable}
{simulatorTable}
{gatewayTable}
{serviceTable}
{equipmentTable}
{this.state.currentUser.role === "Admin" && this.state.numberOfExternalICs > 0 ?
<div style={{float: 'left'}}>
<ICAction
runDisabled={this.state.selectedICs.length === 0}
runAction={action => this.runAction(action)}
runAction={(action, when) => this.runAction(action, when)}
actions={[
{id: '-1', title: 'Select command', data: {action: 'none'}},
{id: '-1', title: 'Action', data: {action: 'none'}},
{id: '0', title: 'Reset', data: {action: 'reset'}},
{id: '1', title: 'Shutdown', data: {action: 'shutdown'}},
]}
@ -481,9 +506,10 @@ class InfrastructureComponents extends Component {
<div style={{ clear: 'both' }} />
<NewICDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} />
<NewICDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} managers={this.state.managers} />
<EditICDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} ic={this.state.modalIC} />
<ImportICDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
<DeleteDialog title="infrastructure-component" name={this.state.modalIC.name || 'Unknown'} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
<ICDialog
show={this.state.icModal}
onClose={data => this.closeICModal(data)}
@ -492,7 +518,6 @@ class InfrastructureComponents extends Component {
userRole={this.state.currentUser.role}
sendControlCommand={(command, ic) => this.sendControlCommand(command, ic)}/>
<DeleteDialog title="infrastructure-component" name={this.state.modalIC.name || 'Unknown'} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
</div>
);
}

View file

@ -34,7 +34,8 @@ class NewICDialog extends React.Component {
category: '',
managedexternally: false,
description: '',
location: ''
location: '',
manager: ''
};
}
@ -48,7 +49,8 @@ class NewICDialog extends React.Component {
uuid: this.state.uuid,
managedexternally: this.state.managedexternally,
location: this.state.location,
description: this.state.description
description: this.state.description,
manager: this.state.manager
};
if (this.state.websocketurl != null && this.state.websocketurl !== "" && this.state.websocketurl !== 'http://') {
@ -88,6 +90,7 @@ class NewICDialog extends React.Component {
let websocketurl = true;
let type = true;
let category = true;
let manager = true;
if (this.state.name === '') {
name = false;
@ -97,6 +100,10 @@ class NewICDialog extends React.Component {
uuid = false;
}
if(this.state.managedexternally && manager === ''){
manager = false;
}
if (this.state.type === '') {
type = false;
}
@ -105,7 +112,7 @@ class NewICDialog extends React.Component {
category = false;
}
this.valid = name && uuid && websocketurl && type && category;
this.valid = name && uuid && websocketurl && type && category && manager;
// return state to control
if (target === 'name') return name ? "success" : "error";
@ -113,6 +120,7 @@ class NewICDialog extends React.Component {
if (target === 'websocketurl') return websocketurl ? "success" : "error";
if (target === 'type') return type ? "success" : "error";
if (target === 'category') return category ? "success" : "error";
if (target === 'manager') return manager ? "success" : "error";
return this.valid;
}
@ -131,8 +139,8 @@ class NewICDialog extends React.Component {
case "simulator":
typeOptions = ["dummy","generic","dpsim","rtlab","rscad","opalrt"];
break;
case "controller":
typeOptions = ["kubernetes","villas-controller"];
case "manager":
typeOptions = ["villas-node","villas-relay","generic"];
break;
case "gateway":
typeOptions = ["villas-node","villas-relay"];
@ -146,33 +154,60 @@ class NewICDialog extends React.Component {
default:
typeOptions =[];
}
let managerOptions = [];
managerOptions.push(<option default>Select manager</option>);
for (let m of this.props.managers) {
managerOptions.push (
<option key={m.id} value={m.uuid}>{m.name}</option>
);
}
return (
<Dialog show={this.props.show} title="New Infrastructure Component" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.validateForm()}>
<form>
<FormGroup controlId="managedexternally">
<OverlayTrigger key="3" placement={'left'} overlay={<Tooltip id={`tooltip-${"me"}`}>An externally managed component will show up in the list only after a VILLAScontroller for the component type has created the component and cannot be edited by users</Tooltip>} >
<FormCheck type={"checkbox"} label={"Managed externally"} defaultChecked={this.state.managedexternally} onChange={e => this.handleChange(e)}>
</FormCheck>
</OverlayTrigger>
</FormGroup>
{this.props.managers.length > 0 ?
<>
<FormGroup controlId="managedexternally">
<OverlayTrigger key="3" placement={'left'} overlay={<Tooltip id={`tooltip-${"me"}`}>An externally managed component is created and managed by an IC manager via AMQP</Tooltip>} >
<FormCheck type={"checkbox"} label={"Managed externally"} defaultChecked={this.state.managedexternally} onChange={e => this.handleChange(e)}>
</FormCheck>
</OverlayTrigger>
</FormGroup>
{this.state.managedexternally === true ?
<FormGroup controlId="manager" valid={this.validateForm('manager')}>
<OverlayTrigger key="0" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<FormLabel>Manager to create new IC *</FormLabel>
</OverlayTrigger>
<FormControl as="select" value={this.state.manager} onChange={(e) => this.handleChange(e)}>
{managerOptions}
</FormControl>
</FormGroup>
: <div/>
}
</>
: <div/>
}
<FormGroup controlId="name" valid={this.validateForm('name')}>
<OverlayTrigger key="0" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<OverlayTrigger key="1" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<FormLabel>Name *</FormLabel>
</OverlayTrigger>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="category" valid={this.validateForm('category')}>
<OverlayTrigger key="1" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<OverlayTrigger key="2" placement={'right'} overlay={<Tooltip id={`tooltip-${"required"}`}> Required field </Tooltip>} >
<FormLabel>Category of component *</FormLabel>
</OverlayTrigger>
<FormControl as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
<option default>Select category</option>
<option>simulator</option>
<option>controller</option>
<option>service</option>
<option>gateway</option>
<option>equipment</option>
<option>manager</option>
</FormControl>
</FormGroup>
<FormGroup controlId="type" valid={this.validateForm('type')}>
@ -206,11 +241,15 @@ class NewICDialog extends React.Component {
<FormControl type="text" placeholder="Enter Description" value={this.state.description} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="uuid" valid={this.validateForm('uuid')}>
<FormLabel>UUID</FormLabel>
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
{this.state.managedexternally === false ?
<FormGroup controlId="uuid" valid={this.validateForm('uuid')}>
<FormLabel>UUID</FormLabel>
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid}
onChange={(e) => this.handleChange(e)}/>
<FormControl.Feedback/>
</FormGroup>
: <div/>
}
</form>
</Dialog>
);

View file

@ -390,9 +390,7 @@ class Scenario extends React.Component {
}
runAction(action, delay) {
// delay in seconds
runAction(action, when) {
if (action.data.action === 'none') {
console.warn("No command selected. Nothing was sent.");
return;
@ -415,8 +413,7 @@ class Scenario extends React.Component {
action.data.parameters = this.state.configs[index].startParameters;
}
// Unix time stamp + delay
action.data.when = Math.round(Date.now() / 1000.0 + delay)
action.data.when = when;
console.log("Sending action: ", action.data)
@ -798,8 +795,6 @@ class Scenario extends React.Component {
scenarioID={this.state.scenario.id}
/>
{/*Component Configurations table*/}
<h2 style={tableHeadingStyle}>Component Configurations
<OverlayTrigger
@ -866,9 +861,9 @@ class Scenario extends React.Component {
<div style={{float: 'left'}}>
<ICAction
runDisabled={this.state.selectedConfigs.length === 0}
runAction={(action, delay) => this.runAction(action, delay)}
runAction={(action, when) => this.runAction(action, when)}
actions={[
{id: '-1', title: 'Select command', data: {action: 'none'}},
{id: '-1', title: 'Action', data: {action: 'none'}},
{id: '0', title: 'Start', data: {action: 'start'}},
{id: '1', title: 'Stop', data: {action: 'stop'}},
{id: '2', title: 'Pause', data: {action: 'pause'}},